Merge branch 'master' of feistymeow.org:feisty_meow
[feisty_meow.git] / kona / src / org / feistymeow / networking / BasicWebServer.java
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
10 /**
11  * Provides a lightweight way for RNS structures to be accessible over http.
12  * 
13  * @Author Chris Koeritz
14  */
15
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 }