📄 client.java
字号:
// Client.java// $Id: Client.java,v 1.77 2003/02/25 16:53:53 ylafon Exp $// (c) COPYRIGHT MIT and INRIA, 1996.// Please first read the full copyright statement in file COPYRIGHT.htmlpackage org.w3c.jigsaw.http;import java.io.DataOutputStream;import java.io.InputStream;import java.io.IOException;import java.io.PipedInputStream;import java.net.InetAddress;import org.w3c.www.http.HTTP;import org.w3c.www.http.HttpParserException;import org.w3c.www.mime.MimeParser;import org.w3c.www.mime.MimeParserException;import org.w3c.www.mime.MimeParserFactory;import org.w3c.tools.resources.ProtocolException;import org.w3c.tools.resources.ResourceException;import org.w3c.tools.timers.EventHandler;import org.w3c.jigsaw.servlet.JigsawHttpServletResponse;import org.w3c.jigsaw.servlet.ServletWrapper;/** * The request timeout event, to be delivered by the timer package. * Handling timers is expensive, and can be a bottleneck, this is the only * event Jigsaw will set during processing a request. * No other timeing events are used (ie closing a persistent connection is * not triggered by some timing, but rather by the load of the server). */class RequestTimeout { Client client = null; RequestTimeout (Client client) { this.client = client; }}/** * Client instances keep track of a specific connection with a browser. * This abstract class is responsible for handling an HTTP connection, as * described by an input and output stream, from right after the connection * is accepted, until the connection has to be shutdown. It provides * all the methods to run the HTTP dialog, and leave it to subclasses to * implement the accept connection and the persistent connections strategy. * <p>Customizing this class is done by subclassing it, and implementing * the abstract methods. * <p>For sample implementations, you can check the socket and mux sub * packages. * @see ClientFactory * @see org.w3c.jigsaw.http.socket.SocketClient * @see org.w3c.jigsaw.http.mux.MuxClient */public abstract class Client implements EventHandler { private static final boolean debuglog = false; private final static byte hexaTable[] = { (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F' }; /** * The continue reply, if ever needed is created only once. */ private Reply contreply = null; /** * The Mime factory instance we use to create requests. */ private MimeParserFactory factory = null; /** * The uniq integer identifier for that client. */ protected int identifier = -1; /** * The server context responsible for that client. */ protected httpd server = null; /** * Is this client in debug mode ? */ protected boolean debug = false; /** * The buffer used to emit copy data back to the client. */ protected byte buffer[] = null ; /** * Is this client currently <em>running</em>. * A client starts <em>running</em> when its <code>startConnection</code> * method is invoked, with the HTTP transport streams. It stops running * either because of an interruption, triggered by a call to * <code>interruptConnection</code> or by the <code>idleConnection</code>, * returning <strong>true</strong>, or because the connection has to be * closed. * <p>In all these cases, the <code>stopConnection</code> method is * invoked. */ private boolean running = false; /** * When runnin, the HTTP major version used to discuss with that client. */ private short major = -1; /** * WHen running, the HTTP minor version used to discuss with that client. */ private short minor = -1; /** * When running, the input stream from which this client gets HTTP. */ private InputStream input = null ; /** * When running, the output stream to which this client emits HTTP. */ private DataOutputStream output = null ; /** * When processing a request, the handle to the pending timer. */ private Object timer = null; /** * When running, the Mime parser instance to parse current input stream. */ private MimeParser parser = null; /** * When running, the interrupt flag. */ private boolean interrupted = false; /** * Number of requests handled within this client context. */ protected int reqcount = 0; /** * flags to avoid multiple 100-Continue during multiple stages of a * request */ protected boolean cont = false; /** * The number of bytes in the body of the previously handled request */ protected long prev_body_count = 0; /** * HTTP lenient mode? */ private boolean lenient = true; /** * Sets this client next timer. * Timers are used only to limit the duration of a request processing. * Only one timer can be pending at any time for a given client, * setting some new timer if one is already pending will kill * the previous one. * @param ms The number of milliseconds after which the timer should * expire. * @param data The call data for the tevent timer handler. */ private synchronized void setTimeout (int ms, Object data) { if ( timer != null ) { server.timer.recallTimer (timer) ; timer = null ; } timer = server.timer.registerTimer (ms, this, data) ; } /** * Remove any pending timer. */ private synchronized void removeTimeout() { if ( timer != null ) server.timer.recallTimer (timer) ; } /** * Handle timer events. * For the time being, timer events are only used * to detect an overrunning request, so this handler just kills the * correponding client. * @param data The timer closure. * @param time The absolute time at which the event was triggered. * @see org.w3c.tools.timers.EventManager * @see org.w3c.tools.timers.EventHandler */ public synchronized void handleTimerEvent (Object data, long time) { timer = null ; // This request has taken to long to fullfill, abort it Reply abort = new Reply (this) ; abort.setStatus (HTTP.REQUEST_TIMEOUT) ; interruptConnection(true); } /** * Terminate the connection that is currently handled. * This method closes the currently handled input and output stream, * removes any pending timers and cleanup the client state, so it is ready * to handle any new connection. * If the client is bound to a thread, the thread is throw an exception at */ private synchronized void terminate() { if ( ! running ) return ; removeTimeout(); try { if ( output != null ) { output.flush(); output.close(); } } catch (IOException ex) { } try { if ( input != null ) input.close(); } catch (IOException ex) { } input = null; output = null; parser = null; major = -1; minor = -1; interrupted = false; running = false; } /** * Request has been processed into Reply, should we keep connection alive ? * Test wether we can keep the connection alive, after the given * reply has been emited. * @param request The request to examine. * @param reply Its computed reply. */ protected boolean tryKeepConnection (Request request, Reply reply) { // The server doesn't want any keep connection if ( ! server.getClientKeepConnection() ) return false ; if (!request.canKeepConnection()) { if (reply.tryKeepConnection()) { reply.addConnection("close"); } return false; } return reply.tryKeepConnection(); } /** * Read the next request from our current input stream. * @return a Request instance, or <strong>null</strong> if interrupted. * @exception IOException If some IO error occured. * @exception ClientException If either an IO error happened or bad * HTTP was received. In both cases the connection needs to be closed. */ protected Request getNextRequest () throws ClientException, IOException { Request request = null ; cont = false; // reinit the continue try { request = (Request) parser.parse(lenient); } catch (IOException ex) { // The connection has probably been closed prematurely: return null; } catch (HttpParserException ex) { if ( debug ) { System.out.println("+++ "+this+" got exception:"); ex.printStackTrace(); } throw new ClientException(this, ex); } catch (MimeParserException ex) { if ( debug ) { System.out.println("+++ "+this+" got exception:"); ex.printStackTrace(); } throw new ClientException (this, ex); } if ( debug ) request.dump(System.out) ; return request ; } /** * Run chunk encoding on the provided stream to emit reply's body. * @param is The reply's body that has to be chunk encoded. * @exception IOException If IO error occurs. */ protected int chunkTransfer(InputStream is, Reply reply) throws IOException { byte zeroChunk[] = { ((byte) 48), ((byte) 13), ((byte) 10) };//0\r\n byte crlf[] = { ((byte) 13), ((byte) 10) } ; byte bheader[] = new byte[32] ; int blen = 0 ; int written = 0 ; int got = 0 ; int sgot; String header = null ; try { // Emit the reply stream: while (got >= 0) { if (got == 0) { try { got = is.read(buffer); } catch (IOException ioex) { if (reply.hasState(ServletWrapper.RUNNER) && (is instanceof PipedInputStream) ) { // here, the problem may be that multiple // threads are writing to the PipedOutputStream // and the IOError may exists if (!reply.hasState(ServletWrapper.ENDED)) { got = 0; continue; } } throw ioex; } continue; } // Emit a full chunk: header first, followed by the body // we dump the hexa size of the header backward sgot = got; blen = 3; bheader[30] = ((byte) 13); // \r bheader[31] = ((byte) 10); // \n while (sgot > 15) { bheader[32-blen] = hexaTable[sgot % 16]; sgot >>= 4; blen++; } bheader[32-blen] = hexaTable[sgot]; output.write(bheader, 32-blen, blen) ; output.write(buffer, 0, got) ; output.write(crlf, 0, 2) ; output.flush() ; written += (blen+got) ; try { got = is.read(buffer); } catch (IOException ioex) { if (reply.hasState(ServletWrapper.RUNNER) && (is instanceof PipedInputStream) ) { // here, the problem may be that multiple // threads are writing to the PipedOutputStream // and the IOError may exists if (!reply.hasState(ServletWrapper.ENDED)) { got = 0; continue; } } throw ioex; } } } catch (IOException ex) { // To cope with Java's exec bug // Anyway, if this really fails, the output will fail below too if (debug) { ex.printStackTrace(); } } // Emit the 0 chunk: output.write(zeroChunk, 0, 3) ; // FIXME trailers should be sent here output.write(crlf, 0, 2) ; output.flush() ; return written + blen ; } /** * Emit the given reply to the client. * @param reply The reply to be emited. * @return The number of body bytes emited, or <strong>-1</strong> if * no bytes needed to be emitted. * @exception IOException If some IO error occurs. */ protected int emitReply (Reply reply) throws IOException { boolean chunkable = false ; // Emit the reply if needed: if ( reply.getStatus() == HTTP.DONE ) return -1; InputStream is = reply.openStream() ; if ( is == null ) { if ( debug ) reply.dump(System.out); reply.emit(output); return -1; } else { chunkable = reply.canChunkTransfer() ; if ( debug ) reply.dump(System.out); if ( reply.getStatus() != HTTP.NOHEADER ) reply.emit(output) ; } // No shuffler available, perform the job ourselves. if ( buffer == null ) buffer = new byte[getServer().getClientBufferSize()] ; // Check if we can chunk the reply back: int written = 0 ; try { if ( chunkable ) { written = chunkTransfer(is, reply) ; } else { int got = 0 ; while (got >= 0) { output.write (buffer, 0, got) ; written += got ; try { got = is.read(buffer, 0, buffer.length); } catch (IOException ioex) { if (reply.hasState(ServletWrapper.RUNNER) && (is instanceof PipedInputStream) ) { // here, the problem may be that multiple // threads are writing to the PipedOutputStream // and the IOError may exists if (!reply.hasState(ServletWrapper.ENDED)) { got = 0; continue; } } throw ioex; } } } } finally { is.close() ; } return written ; } /** * Process a request. * This methods processs the request to the point that a reply is * available. This methods sets a timeout, to limit the duration of this * request processing. * @param request The request to process. * @exception ClientException If either the timeout expires or the entity * was unable to handle the request. */ protected Reply processRequest (Request request) throws ClientException { Reply reply = null ; setTimeout(server.getRequestTimeOut(), new RequestTimeout(this)) ; try { reply = (Reply)server.perform(request); } catch (ProtocolException ex) { if ( debug ) { System.out.println("+++ "+this+" got exception:"); ex.printStackTrace(); } if ((reply != null) && reply.hasStream()) { try { reply.openStream().close(); } catch (Exception cex) {} } if ( ex.hasReply () ) { return (Reply) ex.getReply() ; } else { throw new ClientException (this, ex) ; } } catch (ResourceException ex2) { throw new ClientException(this, ex2); } if ( reply == null ) { String errmsg = "target resource emited a null Reply."; throw new ClientException (this, errmsg); } reqcount++; return reply ; } /** * Start processing the given connection. * This is the entry point for sub classes, in order to make the client * start processing the HTTP protocol on the given input and output * streams. * <p>Before this method returns, both provided streams are <em>always * </em> closed, and the <code>stopConnection</code> method invoked. * @param in The input stream to receive HTTP requests. * @param out The output stream to send HTTP replies. * @return A boolean <strong>true</strong> if this method returns because * of an interruption, <strong>false</strong> otherwsie (ie the connection * was gracefully shutdown). * @exception ClientException If some severe error has occured and the * current connection needs to be terminated.
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -