feisty meow concerns codebase  2.140
BasicWebServer.java
Go to the documentation of this file.
1 package org.feistymeow.networking;
2 
3 import java.util.*;
4 import java.io.*;
5 import java.net.*;
6 
7 import org.apache.commons.logging.Log;
8 import org.apache.commons.logging.LogFactory;
9 
16 /*
17  * original example thanks to Matt Mahoney, at
18  * http://cs.fit.edu/~mmahoney/cse3103/java/Webserver.java
19  */
20 
21 public class BasicWebServer
22 {
23  static private Log logger = LogFactory.getLog(BasicWebServer.class);
24 
25  private int port;
26  private boolean leaving = false; // turns to true when should stop serving.
27  servingThread socketThread;
28  private ServerSocket realSocket;
29 
30  BasicWebServer(int portIn)
31  {
32  port = portIn;
33  }
34 
35  public void shutDown()
36  {
37  leaving = true;
38  if (realSocket != null) {
39  try {
40  realSocket.close();
41  } catch (IOException e) {
42  }
43  }
44  if (socketThread != null) {
45  // stop it?
46  }
47  }
48 
49  public class servingThread implements Runnable
50  {
51  private Thread thread;
52  private ServerSocket serverSocket;
53 
54  servingThread(ServerSocket socket)
55  {
56  serverSocket = socket;
57  thread = new Thread(this);
58  thread.start();
59  }
60 
61  @Override
62  public void run()
63  {
64  while (!leaving) {
65  try {
66  logger.debug("about to accept on server socket.");
67  Socket s = serverSocket.accept(); // Wait for a client to
68  // connect
69  logger.debug("accepted client, spawning handler.");
70  new ClientHandler(s); // Handle the client in a separate
71  // thread
72  } catch (Throwable cause) {
73  logger.error("exception raised while handling accepted socket", cause);
74  }
75  }
76  }
77  }
78 
79  // enums for outcomes? really need better reporting.
80  public int startServing()
81  {
82  if (socketThread != null)
83  return 1; // already running outcome.
84  try {
85  realSocket = new ServerSocket(port);
86  } catch (Throwable cause) {
87  logger.error("failure to start server on port " + port, cause);
88  return 1;
89  // socket failure outcome.
90  }
91  socketThread = new servingThread(realSocket);
92  return 0;
93  }
94 
95  public String predictMimeType(String filename)
96  {
97 
98  // kludge to try one type:
99  return "text/plain;charset=utf-8";
100 
101  /*
102  *
103  * if (filename.endsWith(".html") || filename.endsWith(".htm")) return "text/html"; if
104  * (filename.endsWith(".jpg") || filename.endsWith(".jpeg")) return "image/jpeg"; if
105  * (filename.endsWith(".gif")) return "image/gif"; if (filename.endsWith(".class")) return
106  * "application/octet-stream"; return "text/plain";
107  */
108  }
109 
110  // A ClientHandler reads an HTTP request and responds
111  class ClientHandler extends Thread
112  {
113  private Socket socket; // The accepted socket from the Webserver
114 
115  // Start the thread in the constructor
116  public ClientHandler(Socket s)
117  {
118  socket = s;
119  start();
120  }
121 
122  // Read the HTTP request, respond, and close the connection
123  public void run()
124  {
125  try {
126  logger.debug("into client run(): listening for gets.");
127 
128  // Open connections to the socket
129  BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
130  PrintStream out = new PrintStream(new BufferedOutputStream(socket.getOutputStream()));
131 
132  // Read filename from first input line "GET /filename.html ..."
133  // or if not in this format, treat as a file not found.
134  String s = in.readLine();
135  logger.debug("request is: " + s); // Log the request
136 
137  // Attempt to serve the file. Catch FileNotFoundException and
138  // return an HTTP error "404 Not Found". Treat invalid requests
139  // the same way.
140  String filename = "";
141  StringTokenizer st = new StringTokenizer(s);
142  try {
143 
144  boolean transferFile = true;
145  // get the command first.
146  String command = st.nextToken();
147  // Parse the filename from the command.
148  if (st.hasMoreElements() && command.equalsIgnoreCase("GET") && st.hasMoreElements()) {
149  filename = st.nextToken();
150  } else if (st.hasMoreElements() && command.equalsIgnoreCase("HEAD") && st.hasMoreElements()) {
151  filename = st.nextToken();
152  transferFile = false; // don't need to do that, just the
153  // header.
154  } else {
155  logger.error("going to blow file not found exception now.");
156  throw new FileNotFoundException(); // Bad request
157  }
158  logger.info("filename to handle is now: " + filename);
159 
160  // Append trailing "/" with "index.html"
161  // /hmmm: may want to make this assume directory.
162  if (filename.endsWith("/"))
163  logger.error("unhandled attempt to get item ending in slash");
164  // if (filename.endsWith("/"))
165  // filename += "index.html";
166 
167  // Remove leading / from filename
168  // / while (filename.indexOf("/") == 0)
169  // / filename = filename.substring(1);
170 
171  // Replace "/" with "\" in path for PC-based servers
172  filename = filename.replace('/', File.separator.charAt(0));
173 
174  logger.info("asking for rns path of " + filename);
175 
176  // Check for illegal characters to prevent access to
177  // superdirectories
178  if (filename.indexOf("..") >= 0 || filename.indexOf(':') >= 0 || filename.indexOf('|') >= 0)
179  throw new FileNotFoundException();
180 
181  logger.info("got past filename checks for: " + filename);
182 
183  /*
184  * this doesn't actually check that trailing slash is missing! // If a directory
185  * is requested and the trailing / is missing, // send the client an HTTP
186  * request to append it. (This is // necessary for relative links to work
187  * correctly in the client). if ((new GeniiPath(filename)).isDirectory()) {
188  * out.print("HTTP/1.0 301 Moved Permanently\r\n" + "Location: /" + filename +
189  * "/\r\n\r\n"); out.close(); return; }
190  */
191 
192  // trying to get around worrying about mime types by saying
193  // "just get this there".
194  String mimeType = predictMimeType(filename);
195  // //"application/octet-stream";
196 
197  File source = new File(filename);
198  if (!source.exists()) {
199  logger.error("source does not exist for serving: " + filename);
200  // do something!
201  // hmmm: below could be abstracted to more general
202  // denial method.
203  out.println("HTTP/1.1 404 Not Found\r\n" + "Content-type: text/html\r\n\r\n"
204  + "<html><head></head><body>" + filename + " not found</body></html>\n");
205  out.close();
206 
207  }
208  out.print("HTTP/1.1 200 OK\r\n" + "Content-type: " + mimeType + "\r\n" + "Connection: close" + "\r\n"
209  // // + "\r\nContent-Length: " + source.size? +
210  // "\r\n"
211  + "\r\n");
212  if (!transferFile) {
213  logger.debug("closing stream for finished HEAD request.");
214  out.close();
215  return;
216  }
217  logger.debug("moving to handle GET request.");
218  FileInputStream f = new FileInputStream(filename);
219  logger.debug("opened stream on source");
220  // Send file contents to client, then close the connection.
221  byte[] a = new byte[4096];
222  int n;
223  while ((n = f.read(a)) > 0)
224  out.write(a, 0, n);
225  logger.debug("wrote file back for request, closing stream.");
226  f.close();
227  out.close();
228  } catch (FileNotFoundException x) {
229  logger.error("failed to find requested file: " + filename);
230  out.println("HTTP/1.1 404 Not Found\r\n" + "Content-type: text/html\r\n\r\n" + "<html><head></head><body>"
231  + filename + " not found</body></html>\n");
232  out.close();
233  }
234  } catch (IOException x) {
235  logger.error("exception blew out in outer area of web server", x);
236  // / System.out.println(x);
237  }
238  }
239  }
240 }