ToadletContextImpl.java
| Index Score | ||
|---|---|---|
![]() |
![]() |
freenet.clients.http |
![]() |
![]() |
Freenet |
View: Reasons, Metrics, Source Code
These are the metrics that contribute to the Enerjy Score for this file, ranked by impact. So the metrics listed at the top influence the score to a greater extent that the metrics listed at the bottom.
package freenet.clients.http;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.InetAddress;
import java.net.Socket;
import java.net.URI;
import java.net.URISyntaxException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Enumeration;
import java.util.Locale;
import java.util.TimeZone;
import freenet.l10n.L10n;
import freenet.support.HTMLEncoder;
import freenet.support.HTMLNode;
import freenet.support.Logger;
import freenet.support.MultiValueTable;
import freenet.support.URIPreEncoder;
import freenet.support.api.Bucket;
import freenet.support.api.BucketFactory;
import freenet.support.io.BucketTools;
import freenet.support.io.FileUtil;
import freenet.support.io.LineReadingInputStream;
import freenet.support.io.TooLongException;
/**
* ToadletContext implementation, including all the icky HTTP parsing etc.
* An actual ToadletContext object represents a request, after we have parsed the
* headers. It provides methods to send replies.
* @author root
*
*/
public class ToadletContextImpl implements ToadletContext {
private final MultiValueTable headers;
private final OutputStream sockOutputStream;
private final PageMaker pagemaker;
private final BucketFactory bf;
private final ToadletContainer container;
private final InetAddress remoteAddr;
/** Is the context closed? If so, don't allow any more writes. This is because there
* may be later requests.
*/
private boolean closed;
private boolean shouldDisconnect;
public ToadletContextImpl(Socket sock, MultiValueTable headers, BucketFactory bf, PageMaker pageMaker, ToadletContainer container) throws IOException {
this.headers = headers;
this.closed = false;
sockOutputStream = sock.getOutputStream();
remoteAddr = sock.getInetAddress();
if(Logger.shouldLog(Logger.DEBUG, this))
Logger.debug(this, "Connection from "+remoteAddr);
this.bf = bf;
this.pagemaker = pageMaker;
this.container = container;
}
private void close() {
closed = true;
}
private void sendMethodNotAllowed(String method, boolean shouldDisconnect) throws ToadletContextClosedException, IOException {
if(closed) throw new ToadletContextClosedException();
MultiValueTable mvt = new MultiValueTable();
mvt.put("Allow", "GET, PUT");
sendError(sockOutputStream, 405, "Method Not Allowed", l10n("methodNotAllowed"), shouldDisconnect, mvt);
}
private static String l10n(String key) {
return L10n.getString("ToadletContextImpl."+key);
}
private static String l10n(String key, String pattern, String value) {
return L10n.getString("ToadletContextImpl."+key, new String[] { pattern }, new String[] { value });
}
/**
* Send an error message. Caller provides the HTTP code, reason string, and a message, which
* will become the title and the h1'ed contents of the error page.
*/
private static void sendError(OutputStream os, int code, String httpReason, String message, boolean shouldDisconnect, MultiValueTable mvt) throws IOException {
sendHTMLError(os, code, httpReason, "<html><head><title>"+message+"</title></head><body><h1>"+message+"</h1></body>", shouldDisconnect, mvt);
}
/**
* Send an error message, containing full HTML from a String.
* @param os The OutputStream to send the message to.
* @param code The HTTP status code.
* @param httpReason The HTTP reason string for the HTTP status code. Do not make stuff up,
* use the official reason string, or some browsers may break.
* @param htmlMessage The HTML string to send.
* @param disconnect Whether to disconnect from the client afterwards.
* @param mvt Any additional headers.
* @throws IOException If we could not send the error message.
*/
private static void sendHTMLError(OutputStream os, int code, String httpReason, String htmlMessage, boolean disconnect, MultiValueTable mvt) throws IOException {
if(mvt == null) mvt = new MultiValueTable();
byte[] messageBytes = htmlMessage.getBytes("UTF-8");
sendReplyHeaders(os, code, httpReason, mvt, "text/html; charset=UTF-8", messageBytes.length, null, disconnect);
os.write(messageBytes);
}
private void sendNoToadletError(boolean shouldDisconnect) throws ToadletContextClosedException, IOException {
if(closed) throw new ToadletContextClosedException();
sendError(sockOutputStream, 404, "Not Found", l10n("noSuchToadlet"), shouldDisconnect, null);
}
private static void sendURIParseError(OutputStream os, boolean shouldDisconnect, Throwable e) throws IOException {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
e.printStackTrace(pw);
pw.close();
String message = "<html><head><title>"+l10n("uriParseErrorTitle")+"</title></head><body><p>"+HTMLEncoder.encode(e.getMessage())+"</p><pre>\n"+sw.toString();
sendHTMLError(os, 400, "Bad Request", message, shouldDisconnect, null);
}
public void sendReplyHeaders(int replyCode, String replyDescription, MultiValueTable mvt, String mimeType, long contentLength) throws ToadletContextClosedException, IOException {
sendReplyHeaders(replyCode, replyDescription, mvt, mimeType, contentLength, null);
}
public void sendReplyHeaders(int replyCode, String replyDescription, MultiValueTable mvt, String mimeType, long contentLength, Date mTime) throws ToadletContextClosedException, IOException {
if(closed) throw new ToadletContextClosedException();
sendReplyHeaders(sockOutputStream, replyCode, replyDescription, mvt, mimeType, contentLength, mTime, shouldDisconnect);
}
public PageMaker getPageMaker() {
return pagemaker;
}
public MultiValueTable getHeaders() {
return headers;
}
static void sendReplyHeaders(OutputStream sockOutputStream, int replyCode, String replyDescription, MultiValueTable mvt, String mimeType, long contentLength, Date mTime, boolean disconnect) throws IOException {
// Construct headers
if(mvt == null)
mvt = new MultiValueTable();
if(mimeType != null)
if(mimeType.equalsIgnoreCase("text/html")){
mvt.put("content-type", mimeType+"; charset=UTF-8");
}else{
mvt.put("content-type", mimeType);
}
if(contentLength >= 0)
mvt.put("content-length", Long.toString(contentLength));
String expiresTime;
if (mTime == null) {
expiresTime = "Thu, 01 Jan 1970 00:00:00 GMT";
} else {
// use an expiry time of 1 day, somewhat arbitrarily
expiresTime = makeHTTPDate(mTime.getTime() + (24 * 60 * 60 * 1000));
}
mvt.put("expires", expiresTime);
String nowString = makeHTTPDate(System.currentTimeMillis());
String lastModString;
if (mTime == null) {
lastModString = nowString;
} else {
lastModString = makeHTTPDate(mTime.getTime());
}
mvt.put("last-modified", lastModString);
mvt.put("date", nowString);
if (mTime == null) {
mvt.put("pragma", "no-cache");
mvt.put("cache-control", "max-age=0, must-revalidate, no-cache, no-store, post-check=0, pre-check=0");
}
if(disconnect)
mvt.put("connection", "close");
else
mvt.put("connection", "keep-alive");
StringBuffer buf = new StringBuffer(1024);
buf.append("HTTP/1.1 ");
buf.append(replyCode);
buf.append(' ');
buf.append(replyDescription);
buf.append("\r\n");
for(Enumeration e = mvt.keys();e.hasMoreElements();) {
String key = (String) e.nextElement();
Object[] list = mvt.getArray(key);
key = fixKey(key);
for(int i=0;i<list.length;i++) {
String val = (String) list[i];
buf.append(key);
buf.append(": ");
buf.append(val);
buf.append("\r\n");
}
}
buf.append("\r\n");
sockOutputStream.write(buf.toString().getBytes("US-ASCII"));
}
static TimeZone TZ_UTC = TimeZone.getTimeZone("UTC");
private static String makeHTTPDate(long time) {
// For HTTP, GMT == UTC
SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'",Locale.US);
sdf.setTimeZone(TZ_UTC);
return sdf.format(new Date(time));
}
/** Fix key case to be conformant to HTTP expectations.
* Note that HTTP is case insensitive on header names, but we may as well
* send something as close to the spec as possible in case of broken clients...
*/
private static String fixKey(String key) {
StringBuffer sb = new StringBuffer(key.length());
char prev = 0;
for(int i=0;i<key.length();i++) {
char c = key.charAt(i);
if((i == 0) || (prev == '-')) {
c = Character.toUpperCase(c);
}
sb.append(c);
prev = c;
}
return sb.toString();
}
/**
* Handle an incoming connection. Blocking, obviously.
*/
public static void handle(Socket sock, ToadletContainer container, PageMaker pageMaker) {
try {
InputStream is = new BufferedInputStream(sock.getInputStream(), 4096);
LineReadingInputStream lis = new LineReadingInputStream(is);
while(true) {
String firstLine = lis.readLine(32768, 128, false); // ISO-8859-1 or US-ASCII, _not_ UTF-8
if (firstLine == null) {
sock.close();
return;
} else if (firstLine.equals("")) {
continue;
}
boolean logMINOR = Logger.shouldLog(Logger.MINOR, ToadletContextImpl.class);
if(logMINOR)
Logger.minor(ToadletContextImpl.class, "first line: "+firstLine);
String[] split = firstLine.split(" ");
if(split.length != 3)
throw new ParseException("Could not parse request line (split.length="+split.length+"): "+firstLine);
if(!split[2].startsWith("HTTP/1."))
throw new ParseException("Unrecognized protocol "+split[2]);
URI uri;
try {
uri = URIPreEncoder.encodeURI(split[1]).normalize();
if(logMINOR) Logger.minor(ToadletContextImpl.class, "URI: "+uri+" path "+uri.getPath()+" host "+uri.getHost()+" frag "+uri.getFragment()+" port "+uri.getPort()+" query "+uri.getQuery()+" scheme "+uri.getScheme());
} catch (URISyntaxException e) {
sendURIParseError(sock.getOutputStream(), true, e);
return;
}
String method = split[0];
MultiValueTable headers = new MultiValueTable();
while(true) {
String line = lis.readLine(32768, 128, false); // ISO-8859 or US-ASCII, not UTF-8
if (line == null) {
sock.close();
return;
}
//System.out.println("Length="+line.length()+": "+line);
if(line.length() == 0) break;
int index = line.indexOf(':');
if (index < 0) {
throw new ParseException("Missing ':' in request header field");
}
String before = line.substring(0, index).toLowerCase();
String after = line.substring(index+1);
after = after.trim();
headers.put(before, after);
}
boolean disconnect = shouldDisconnectAfterHandled(split[2].equals("HTTP/1.0"), headers) || !container.enablePersistentConnections();
boolean allowPost = container.allowPosts();
BucketFactory bf = container.getBucketFactory();
ToadletContextImpl ctx = new ToadletContextImpl(sock, headers, bf, pageMaker, container);
ctx.shouldDisconnect = disconnect;
/*
* if we're handling a POST, copy the data into a bucket now,
* before we go into the redirect loop
*/
Bucket data;
if(method.equals("POST")) {
String slen = (String) headers.get("content-length");
if(slen == null) {
sendError(sock.getOutputStream(), 400, "Bad Request", l10n("noContentLengthInPOST"), true, null);
return;
}
long len;
try {
len = Integer.parseInt(slen);
if(len < 0) throw new NumberFormatException("content-length less than 0");
} catch (NumberFormatException e) {
sendError(sock.getOutputStream(), 400, "Bad Request", l10n("cannotParseContentLengthWithError", "error", e.toString()), true, null);
return;
}
if(allowPost) {
data = bf.makeBucket(len);
BucketTools.copyFrom(data, is, len);
} else {
FileUtil.skipFully(is, len);
ctx.sendMethodNotAllowed("POST", true);
ctx.close();
return;
}
} else {
// we're not doing to use it, but we have to keep
// the compiler happy
data = null;
}
// Handle it.
boolean redirect = true;
while (redirect) {
// don't go around the loop unless set explicitly
redirect = false;
Toadlet t;
try {
t = container.findToadlet(uri);
} catch (PermanentRedirectException e) {
Toadlet.writePermanentRedirect(ctx, "Found elsewhere", e.newuri.toASCIIString());
break;
}
if(t == null) {
ctx.sendNoToadletError(ctx.shouldDisconnect);
break;
}
HTTPRequestImpl req = new HTTPRequestImpl(uri, data, ctx);
try {
if(method.equals("GET")) {
t.handleGet(uri, req, ctx);
ctx.close();
} else if(method.equals("PUT")) {
t.handlePut(uri, ctx);
ctx.close();
} else if(method.equals("POST")) {
t.handlePost(uri, req, ctx);
} else {
ctx.sendMethodNotAllowed(method, ctx.shouldDisconnect);
ctx.close();
}
} catch (RedirectException re) {
uri = re.newuri;
redirect = true;
} finally {
req.freeParts();
}
}
if(ctx.shouldDisconnect) {
sock.close();
return;
}
}
} catch (ParseException e) {
try {
sendError(sock.getOutputStream(), 400, "Bad Request", l10n("parseErrorWithError", "error", e.getMessage()), true, null);
} catch (IOException e1) {
// Ignore
}
} catch (TooLongException e) {
try {
sendError(sock.getOutputStream(), 400, "Bad Request", l10n("headersLineTooLong"), true, null);
} catch (IOException e1) {
// Ignore
}
} catch (IOException e) {
return;
} catch (ToadletContextClosedException e) {
Logger.error(ToadletContextImpl.class, "ToadletContextClosedException while handling connection!");
return;
} catch (Throwable t) {
Logger.error(ToadletContextImpl.class, "Caught error: "+t+" handling socket", t);
}
}
/**
* Should the connection be closed after handling this request?
* @param isHTTP10 Did the client specify HTTP/1.0?
* @param headers Client headers.
* @return True if the connection should be closed.
*/
private static boolean shouldDisconnectAfterHandled(boolean isHTTP10, MultiValueTable headers) {
String connection = (String) headers.get("connection");
if(connection != null) {
if(connection.equalsIgnoreCase("close"))
return true;
if(connection.equalsIgnoreCase("keep-alive"))
return false;
}
if(isHTTP10 == true)
return true;
else
// HTTP 1.1
return false;
}
static class ParseException extends Exception {
private static final long serialVersionUID = -1;
ParseException(String string) {
super(string);
}
}
public void writeData(byte[] data, int offset, int length) throws ToadletContextClosedException, IOException {
if(closed) throw new ToadletContextClosedException();
sockOutputStream.write(data, offset, length);
}
public void writeData(byte[] data) throws ToadletContextClosedException, IOException {
writeData(data, 0, data.length);
}
public void writeData(Bucket data) throws ToadletContextClosedException, IOException {
if(closed) throw new ToadletContextClosedException();
BucketTools.copyTo(data, sockOutputStream, Long.MAX_VALUE);
}
public BucketFactory getBucketFactory() {
return bf;
}
public HTMLNode addFormChild(HTMLNode parentNode, String target, String name) {
return container.addFormChild(parentNode, target, name);
}
public boolean isAllowedFullAccess() {
return container.isAllowedFullAccess(remoteAddr);
}
public boolean doRobots() {
return container.doRobots();
}
public void forceDisconnect() {
this.shouldDisconnect = true;
}
public ToadletContainer getContainer() {
return container;
}
}
The table below shows all metrics for ToadletContextImpl.java.




