001: // $Id: ServerThread.java,v 1.42 2001/01/23 03:08:28 nconway Exp $
002: package tornado;
003:
004: import java.net.*;
005: import java.io.*;
006: import java.util.ArrayList;
007: import java.util.Date;
008:
009: public class ServerThread extends Thread {
010: private final static int READ_BLOCK = 8192;
011:
012: // per-client resources: these are recreated for every connection
013: private Request request;
014: private Response response;
015: /** The file asked for by the client, if appropriate.*/
016: private File requestFile;
017: /** The message in the access log for this request.*/
018: private CommonLogMessage accessLog;
019:
020: // per-server resources: these are created once per ServerThread
021: private final ArrayList taskPool;
022: private final ServerPool serverPool;
023:
024: /** Constructs a new ServerThread using the specified values. This should
025: * only rarely be called directly: for most purposes, you should spawn
026: * new threads using {@link tornado.ServerPool#addThread()}.
027: */
028: ServerThread(ThreadGroup group, ArrayList tPool, ServerPool sPool) {
029: super (group, "");
030: taskPool = tPool;
031: serverPool = sPool;
032: }
033:
034: /** Begins an infinite loop waiting for connections and serving them.*/
035: public void run() {
036: Socket socket;
037: while (true) {
038: synchronized (taskPool) {
039: /* Wait until we find an incoming connection. If
040: * pool is non-empty, there is already a connection
041: * waiting to be processed, so we can skip the wait().*/
042: while (taskPool.isEmpty()) {
043: try {
044: taskPool.wait();
045: } catch (InterruptedException e) {
046: /* We were interrupted by another thread. In the
047: * current design, this means the ServerPool wants
048: * us to die.*/
049: return;
050: }
051: }
052: // finally, we have an incoming connection
053: socket = (Socket) taskPool.remove(0);
054: }
055:
056: // start the HTTP transaction with the client
057: try {
058: request = new Request(socket);
059: response = new Response(request);
060: accessLog = new CommonLogMessage(request);
061: handOffRequest();
062: } catch (HTTPException e) {
063: // we got a protocol error of some kind - expected
064: sendErrorPage(e);
065: } catch (Exception e) {
066: // we got a more serious error - these should not occur
067: e.printStackTrace();
068: } finally {
069: Tornado.log.logAccess(accessLog);
070: finishConnection();
071: }
072: }
073: }
074:
075: /** Decides what to do with a connection. This looks at the HTTP
076: * headers sent by the client, sends error pages as necessary, and
077: * then decides which method to use to actually handle this request.
078: */
079: private void handOffRequest() throws HTTPException {
080: String method = request.getType();
081:
082: if (method == null) {
083: return;
084: } else if (method.equals("GET")) {
085: handleGetRequest();
086: } else if (method.equals("HEAD")) {
087: handleHeadRequest();
088: } else if (method.equals("POST")) {
089: handlePostRequest();
090: } else if (method.equals("PUT")) {
091: handlePutRequest();
092: } else if (method.equals("OPTIONS")) {
093: handleOptionsRequest();
094: } else if (method.equals("DELETE")) {
095: handleDeleteRequest();
096: } else if (method.equals("TRACE")) {
097: handleTraceRequest();
098: } else if (method.equals("CONNECT")) {
099: handleConnectRequest();
100: } else {
101: throw new HTTPException(HTTP.NOT_IMPLEMENTED);
102: }
103: }
104:
105: /** Handles an HTTP GET request from the client.*/
106: private void handleGetRequest() throws HTTPException {
107: describeFile();
108: try {
109: response.finishHeaders();
110: FileInputStream file = new FileInputStream(requestFile);
111: /* Read and send file in blocks.*/
112: byte[] fileData = new byte[READ_BLOCK];
113: for (int i = 0; i < requestFile.length(); i += READ_BLOCK) {
114: int bytesRead = file.read(fileData);
115: response.rawOut.write(fileData, 0, bytesRead);
116: }
117: file.close();
118: } catch (IOException e) {
119: e.printStackTrace();
120: }
121: }
122:
123: /** Handles an HTTP HEAD request from the client.*/
124: private void handleHeadRequest() throws HTTPException {
125: describeFile();
126: try {
127: response.finishHeaders();
128: } catch (IOException e) {
129: e.printStackTrace();
130: }
131: }
132:
133: /** Handles an HTTP POST request from the client. Not
134: * implemented yet.
135: */
136: private void handlePostRequest() throws HTTPException {
137: throw new HTTPException(HTTP.NOT_IMPLEMENTED);
138: }
139:
140: /** Handles an HTTP PUT request from the client. Not
141: * implemented yet.
142: */
143: private void handlePutRequest() throws HTTPException {
144: throw new HTTPException(HTTP.NOT_IMPLEMENTED);
145: }
146:
147: /** Handles an HTTP OPTIONS request from the client. Not
148: * implemented yet.
149: */
150: private void handleOptionsRequest() throws HTTPException {
151: throw new HTTPException(HTTP.NOT_IMPLEMENTED);
152: }
153:
154: /** Handles an HTTP DELETE request from the client. Not
155: * implemented yet.
156: */
157: private void handleDeleteRequest() throws HTTPException {
158: throw new HTTPException(HTTP.NOT_IMPLEMENTED);
159: }
160:
161: /** Handles an HTTP TRACE request from the client.*/
162: private void handleTraceRequest() throws HTTPException {
163: try {
164: sendStatus(HTTP.OK);
165: sendBasicHeaders();
166: response.sendHeader("Content-Type: message/http");
167: response.finishHeaders();
168: // echo the client's request back to them
169: response.out.write(request.getRawRequest());
170: } catch (IOException e) {
171: e.printStackTrace();
172: }
173: }
174:
175: /** Handles an HTTP CONNECT request from the client. Not
176: * implemented yet.
177: */
178: private void handleConnectRequest() throws HTTPException {
179: throw new HTTPException(HTTP.NOT_IMPLEMENTED);
180: }
181:
182: /** Sends the headers about the URI the client asked for. This method
183: * does the "legwork" when dealing with files: it takes the URI from
184: * the client, uses {@link #translateURI(String)} to get a
185: * <code>File</code>, checks for errors, and then sends the
186: * relevant headers to the client about the specified URI. It is
187: * used by both the HTTP GET and HEAD methods.
188: */
189: private void describeFile() throws HTTPException {
190: requestFile = translateURI(request.getURI());
191:
192: if (requestFile.exists() == false)
193: throw new HTTPException(HTTP.NOT_FOUND);
194:
195: if (requestFile.canRead() == false)
196: throw new HTTPException(HTTP.FORBIDDEN);
197:
198: try {
199: sendStatus(HTTP.OK);
200: sendBasicHeaders();
201: response.sendHeader("Content-Length: "
202: + requestFile.length());
203: response.sendHeader("Last-Modified: "
204: + HTTP.formatDate(new Date(requestFile
205: .lastModified())));
206: response.sendHeader("Content-Type: "
207: + Tornado.mime.getContentType(requestFile));
208: } catch (IOException e) {
209: e.printStackTrace();
210: }
211: }
212:
213: /** Concludes the work with the client and informs the ServerPool.*/
214: private void finishConnection() {
215: if (response != null) {
216: try {
217: response.finishResponse();
218: } catch (IOException e) {
219: e.printStackTrace();
220: }
221: }
222: serverPool.decrementBusyThreads();
223: }
224:
225: /** Sends the specified HTTP error code page to the client.*/
226: private void sendErrorPage(HTTPException httpE) {
227: try {
228: sendStatus(httpE.getCode());
229: sendBasicHeaders();
230: response.sendHeader("Content-Type: text/html");
231: response.finishHeaders();
232: response.out.write(httpE.getErrorPage());
233: } catch (IOException e) {
234: e.printStackTrace();
235: }
236: }
237:
238: /** Sends the specified HTTP status code to the client. This is just
239: * a simple wrapper over {@link tornado.Response#sendStatus(int)},
240: * with one additional function: it notes the status code in the access
241: * log before sending it.
242: */
243: private void sendStatus(int statusCode) throws IOException {
244: accessLog.setStatusCode(statusCode);
245: response.sendStatus(statusCode);
246: }
247:
248: /** Sends the HTTP headers to the client that are always sent. These
249: * headers are those used in common by all HTTP responses.
250: */
251: private void sendBasicHeaders() throws IOException {
252: response.sendHeader("Date: " + HTTP.formatDate(new Date()));
253: response
254: .sendHeader("Server: " + Tornado.config.getVersionSig());
255: response.sendHeader("Connection: close");
256: }
257:
258: /** Translates the URI to a filename. This takes an <b>absolute</b>
259: * URI, performs some security checks, and translates this URI into
260: * the designated file on the local filesystem. It then returns this
261: * file.
262: */
263: private File translateURI(String uri) throws HTTPException {
264: String relURI = uri.substring(uri.indexOf('/', 7));
265: if (uri.indexOf("..", 1) != -1) {
266: throw new HTTPException(HTTP.NOT_FOUND);
267: }
268: return new File(Tornado.config.getDocumentRoot() + relURI);
269: }
270:
271: }
|