// Copyright (C) 2003-2009 by Object Mentor, Inc. All rights reserved. // Released under the terms of the CPL Common Public License version 1.0. package fitnesse; import fitnesse.components.LogData; import fitnesse.http.HttpException; import fitnesse.http.Request; import fitnesse.http.Response; import fitnesse.http.ResponseSender; import fitnesse.responders.ErrorResponder; import util.Clock; import util.StringUtil; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketException; import java.util.GregorianCalendar; //import fitnesseMain.FitNesseMain; public class FitNesseExpediter implements ResponseSender { private Socket socket; private InputStream input; private OutputStream output; private Request request; private Response response; private FitNesseContext context; protected long requestParsingTimeLimit; private long requestProgress; private long requestParsingDeadline; private volatile boolean hasError; public FitNesseExpediter(Socket s, FitNesseContext context) throws IOException { this.context = context; socket = s; input = s.getInputStream(); output = s.getOutputStream(); requestParsingTimeLimit = 10000; } public void start() { try { Request request = makeRequest(); makeResponse(request); sendResponse(); } catch (SocketException se) { // can be thrown by makeResponse or sendResponse. } catch (Throwable e) { e.printStackTrace(); } } public void setRequestParsingTimeLimit(long t) { requestParsingTimeLimit = t; } public long getRequestParsingTimeLimit() { return requestParsingTimeLimit; } public void send(byte[] bytes) { try { output.write(bytes); output.flush(); } catch (IOException stopButtonPressed_probably) { // TODO: Log this } } public void close() { try { log(socket, request, response); socket.close(); } catch (IOException e) { e.printStackTrace(); } } public Socket getSocket() { return socket; } public Request makeRequest() { request = new Request(input); return request; } public void sendResponse() throws IOException { response.sendTo(this); } private Response makeResponse(Request request) throws SocketException { try { Thread parseThread = createParsingThread(request); parseThread.start(); waitForRequest(request); if (!hasError) response = createGoodResponse(request); } catch (SocketException se) { throw se; } catch (Exception e) { response = new ErrorResponder(e).makeResponse(context, request); } return response; } public Response createGoodResponse(Request request) throws Exception { Response response; if (StringUtil.isBlank(request.getResource()) && StringUtil.isBlank(request.getQueryString())) request.setResource("FrontPage"); Responder responder = context.responderFactory.makeResponder(request, context.root); responder = context.authenticator.authenticate(context, request, responder); response = responder.makeResponse(context, request); response.addHeader("Server", "FitNesse-" + FitNesse.VERSION); response.addHeader("Connection", "close"); return response; } private void waitForRequest(Request request) throws InterruptedException { long now = Clock.currentTimeInMillis(); requestParsingDeadline = now + requestParsingTimeLimit; requestProgress = 0; while (!hasError && !request.hasBeenParsed()) { Thread.sleep(10); if (timeIsUp(now) && parsingIsUnproductive(request)) reportError(408, "The client request has been unproductive for too long. It has timed out and will now longer be processed"); } } private boolean parsingIsUnproductive(Request request) { long updatedRequestProgress = request.numberOfBytesParsed(); if (updatedRequestProgress > requestProgress) { requestProgress = updatedRequestProgress; return false; } else return true; } private boolean timeIsUp(long now) { now = Clock.currentTimeInMillis(); if (now > requestParsingDeadline) { requestParsingDeadline = now + requestParsingTimeLimit; return true; } else return false; } private Thread createParsingThread(final Request request) { Thread parseThread = new Thread() { public synchronized void run() { try { request.parse(); } catch (HttpException e) { reportError(400, e.getMessage()); } catch (Exception e) { reportError(e); } } }; return parseThread; } private void reportError(int status, String message) { try { response = new ErrorResponder(message).makeResponse(context, request); response.setStatus(status); hasError = true; } catch (Exception e) { e.printStackTrace(); } } private void reportError(Exception e) { response = new ErrorResponder(e).makeResponse(context, request); hasError = true; } public static LogData makeLogData(Socket socket, Request request, Response response) { LogData data = new LogData(); data.host = ((InetSocketAddress) socket.getRemoteSocketAddress()).getAddress().getHostAddress(); data.time = new GregorianCalendar(); data.requestLine = request.getRequestLine(); data.status = response.getStatus(); data.size = response.getContentSize(); data.username = request.getAuthorizationUsername(); return data; } public void log(Socket s, Request request, Response response) { if (context.logger != null) context.logger.log(makeLogData(s, request, response)); } }