Source code: raining/server/AdvServer.java
1 /*
2 * $Author: rahul_kumar $
3 * $Id: AdvServer.java,v 1.2 2003/10/18 11:32:15 rahul_kumar Exp rahul $
4 */
5
6 package raining.server;
7 import raining.core.*;
8 import raining.protocol.http.*;
9
10 import java.util.*;
11 import java.nio.channels.*;
12 import java.nio.*;
13 import java.text.SimpleDateFormat;
14 import EDU.oswego.cs.dl.util.concurrent.*;
15
16 /**
17 * A more functional server that permits handlers and protocols to be
18 * defined and passed in.
19 * TODO: refactor the http processing out of here, so it can be reused.
20 * TODO: read from priority file
21 * TODO: handle different mime-types. ??
22 * TODO: handle errors 400: if request not a GET
23 * TODO: 304 if not modified since
24 * TODO: send last-modified
25 * TODO: try later, and close if beyond a limit
26 * TODO: flow control, if beyond a limit, rather than just close conn
27 * TODO: take care of absolute URL
28 *
29 *
30 */
31
32 public class AdvServer extends NioSocket {
33 public static void main (String args[]){
34 try {
35 String host = "localhost";
36 int port = 8080;
37
38 /* read from the properties file */
39 Properties serverProps = new Properties();
40 try {
41 serverProps.load(new java.io.FileInputStream("server.properties"));
42 if (serverProps != null){
43 String sport = serverProps.getProperty("server.port", "8080");
44 port = Integer.parseInt(sport);
45 }
46 } catch (Exception exc) { System.err.println( P+" L 41 EXC:"+ exc.toString()); exc.printStackTrace(); }
47
48 /* if user has overridden port on commandline, use that. */
49 if (args.length > 0){
50 host = args[0];
51 port = Integer.parseInt(args[1]);
52 }
53
54 AdvServer httpserver = new AdvServer(host,port);
55 if (serverProps != null){
56 httpserver.setServerProperties(serverProps);
57 }
58
59 NioSocket.setDebug(false);
60 NioSocket.start();
61 } catch (Exception exc) { System.err.println( P+" Main 22 EXC:"+ exc.toString()); exc.printStackTrace(); }
62 }
63 public static final byte[] http503response;
64 static {
65 http503response = Http503Response.getStaticContent() ;
66 }
67
68
69 /** Constructor taking port and host of server to connect to.
70 * It will listen on port on localhost.
71 * @param host not used
72 * @param port port to listen on
73 */
74 public AdvServer (String host, int port) throws Exception {
75 super();
76 try {
77 this.create_server_socket(port);
78 } catch (Exception exc) { System.err.println( P+" 30 EXC:"+ exc.toString()); exc.printStackTrace(); }
79
80
81
82 }
83 /** Set the properties file to be used by the server.
84 */
85 public void setServerProperties (Properties props){
86 serverProps = props;
87
88 try {
89 if (serverProps.getProperty("server.priority_handler") != null){
90 Class c = Class.forName(serverProps.getProperty("server.priority_handler"));
91 this.setPriorityHandler( (PriorityHandler) c.newInstance() );
92 }
93 } catch (Exception exc)
94 {
95 System.err.println( P+" L 86 EXC:"+ exc.toString()); exc.printStackTrace();
96 }
97 conf_max_connections = Integer.parseInt(serverProps.getProperty("server.max_connections"));
98 conf_try_later = Integer.parseInt(serverProps.getProperty("server.try_later"));
99
100 }
101
102
103 /** when a connection is accepted, create a new NIO object for the
104 * incoming socket.
105 */
106 public void handle_accept(){
107 try {
108 SocketChannel schannel = this.accept();
109
110 /* if this connection exceeds the maximum configured
111 * connections, then close it.
112 */
113 if (activeSocketCount >= conf_max_connections){
114 rejectcount++;
115 try {
116 schannel.close();
117 } catch (Exception exc) { System.err.println( P+" L 108 EXC:"+ exc.toString()); }
118 return;
119 } else if (activeSocketCount >= conf_try_later){
120 // we could create a new Incoming and ask it to
121 // write. I am trying this, hoping it will be lighter.
122 // This write could fail, or write partially - to be
123 // tested.
124 // Since i have not created an NioSocket therefore
125 // activeSocketCount doesnt go up. I could increase it
126 // from here itself - DIRTY then there would be a
127 // discrepancy between the map size and
128 // activeSocketCount!!
129 // If this is a single thread, and i am writing and
130 // closing here itself, then the count can NEVER go beyond try_later
131 // to max. that would only be possible if i made it go
132 // through the normal route, which i want to avoid.
133 try {
134 schannel.configureBlocking(false);
135 int size = schannel.write ( ByteBuffer.wrap(http503response) );
136 rejectcount++;
137 } catch (Exception exc) { System.err.println( P+" L 129 EXC:"+ exc.toString()); }
138 finally {
139 try {
140 schannel.close();
141 } catch (Exception exc) { }
142 }
143 return;
144 }
145 if (handle_priority_on_accept){
146 //priorityHandler = new PriorityHandler(schannel);
147 if (priorityHandler.host_priority(schannel) == -1){
148 schannel.close();
149 return;
150 }
151 // can we put read in thread with LP
152 }
153 Incoming insock = new Incoming (schannel, priorityHandler);
154 insock.connected_time = System.currentTimeMillis();
155
156 insock.handle_read();
157
158 } catch (Exception exc) { System.err.println( "Nio handle accept 675 EXC:"+ exc.toString()); exc.printStackTrace(); }
159 accept_accounting();
160 }
161
162 /** set the given handler as a priorityHandler.
163 * @param ph priorityHandler to set
164 */
165 public void setPriorityHandler( PriorityHandler ph){
166 this.priorityHandler = ph;
167 handle_priority_on_accept = true;
168 }
169
170 //// VARIABLES START
171
172 private final static String P = "AdvServer";
173 /** flag to denote whether priority handling is to be done. */
174 public boolean handle_priority_on_accept = false;
175 public PriorityHandler priorityHandler;
176 /** are we precalculating headers, are we using "stale" headers.
177 * this should be from a props file.
178 */
179 public final static boolean precalcheaders = true;
180 /** the name of the default file to be served in / given in http
181 * request.
182 * TODO this is to be picked from server.properties.
183 */
184 public final static String defaultfile = "index.html";
185 public static Properties serverProps = null;
186 protected int rejectcount = 0;
187 protected int conf_try_later = 99999;
188 protected int conf_max_connections = 99999;
189 } // end of class AdvServer
190
191
192 /** this class represents an incoming TCP socket.
193 */
194 class Incoming extends NioSocket {
195
196 public static final byte[] http400response;
197 public static final byte[] http501response;
198 static {
199 http400response = Http400Response.getStaticContent() ;
200 http501response = Http501Response.getStaticContent() ;
201 }
202 public Incoming (SocketChannel sch, PriorityHandler ph) throws Exception {
203 super(sch);
204 schannel = sch;
205 priorityHandler = ph;
206 }
207 public void setPriorityHandler( PriorityHandler ph){
208 this.priorityHandler = ph;
209 }
210
211 /** Send some data back once the read has terminated.
212 * Usually you
213 * would evaluate the data and then decide what to send back.
214 * This was earlier handle_terminator, now read_complete
215 */
216 public void handle_read_complete(String mdata){
217
218 // this can be used to close the channel if a read has not
219 // completed fast enough.
220 read_complete_time = System.currentTimeMillis();
221 read_complete = true;
222 //System.out.println("handle_read_complete"+this.id);
223 request = new HttpRequest ((SocketChannel)this.channel(), mdata);
224 // i could do this in processor, or here
225 if (!request.getOperation().equals("GET")){
226 try {
227 schannel.configureBlocking(false);
228 int size = schannel.write ( ByteBuffer.wrap(http501response) );
229 rejectcount++;
230 } catch (Exception exc) { System.err.println( P+" L 129 EXC:"+ exc.toString()); }
231 finally {
232 try {
233 schannel.close();
234 } catch (Exception exc) { }
235 }
236 return;
237 }
238 // i feel the next stuff should also go into the thread.
239 // we will add throttling here so this code should not be in
240 // main thread.
241 read_priority = priorityHandler.read_priority(request);
242 if (read_priority == -1){
243 handle_close();
244 }
245 // log to a file the host, timestamp and request.
246 //
247 // launch process, which then registers write with selector as
248 // per reactor
249 try {
250 pool.execute(new Processer(NioSocket.selector, request, this));
251 } catch (Exception exc) { System.err.println( P+":"+this.id+" L 132 EXC:"+ exc.toString()); exc.printStackTrace(); }
252
253 //setSendData("OK.\r\n\r\n"+this.id+"\r\n<html>Now the real stuff starts.</html>\r\n");
254 }
255
256 /** Close the channel when a read completes.
257 * I am putting this here,
258 * just in case i decide to remove it from NioSocket someday.
259 */
260 public void handle_write_complete(){
261 //hwc++;
262 handle_close();
263 }
264
265 /** Is the request from client complete.
266 * We could also kill the
267 * read if the size is beyond what we expect or
268 * taking longer than expected,
269 * to take care of DOS attacks */
270 public boolean is_read_complete(){
271 return (rsb.indexOf(terminator) > -1);
272 }
273
274 public long connected_time;
275 public long read_complete_time;
276 public boolean read_complete = false;
277 public int read_priority;
278 public Request request;
279 public PriorityHandler priorityHandler;
280 protected SocketChannel schannel;
281 public int rejectcount = 0; // this is a 400 reject count - i need to join the 2
282 //public static int hwc = 0; // this is a 400 reject count - i need to join the 2
283 public final static String terminator = "\r\n\r\n";
284 private final static String P = "Incoming";
285
286
287 /** Instance of pooled executor.
288 * We may make one of low prio and one
289 * for high prio jobs.
290 * TODO set constr parameters as per requirement
291 * after studying the docs carefully.
292 * With no parameters, it has no max, a min of 1.
293 */
294 static PooledExecutor pool = new PooledExecutor();
295
296
297 } // end of class Incoming
298
299 /** Processes incoming request as a runnable thread.
300 * This thread is fed to the PooledExecutor.
301 */
302 class Processer implements Runnable {
303 Selector selector;
304 Request request;
305 NioSocket nio;
306 static UrlCache urlCache = new UrlCache(1000);
307
308 public Processer (Selector sel, Request request, NioSocket nio){
309 this.selector = sel;
310 this.request = request;
311 this.nio = nio;
312
313 }
314 public void run() { process_and_handoff(); }
315 /** process incoming request object and
316 * send back result.
317 * We would read from a local file and send back result.
318 * I need to catch a FNF 404 and move things into try blk.
319 */
320 public void process_and_handoff(){
321
322 //byte[] content = null;
323 ByteBuffer content = null;
324 try {
325
326 String file = request.getURL();
327 // pick this up from a file.
328 if (file.length()==0)
329 {
330 // need to handle a bad request.
331 //nio.setSendData(http400response);
332 nio.setSendData( Http400Response.getStaticContent());
333 //rejectcount++;
334
335 }
336 // defaults to come from a file.
337 if (file.length()==1 && file.charAt(0) == '/')
338 file = '/'+ AdvServer.defaultfile;
339 else if (file.charAt(file.length()-1) == '/')
340 file = file + AdvServer.defaultfile;
341
342 // this contains content including a 200 OK response
343 FileInfo fi = urlCache.get(file);
344 String modsince = request.findValue("If-Modified-Since");
345 if (modsince != null){
346 Date sincedate = RFC822Date(modsince);
347 if (!fi.ifModifiedSince(sincedate.getTime())){
348 nio.setSendData( Http304Response.getStaticContent() );
349 return;
350 }
351 }
352 content = fi.getContent();
353 // i am caching the headers, thus i can send the data off
354 if (AdvServer.precalcheaders){
355 nio.setSendData( content );
356 }
357
358 else {
359
360 // XXX this is where a gathering write will be abso
361 // essential
362 Response response = new HttpOKResponse(request,content.array(), fi.lastModified);
363 nio.setSendData(response.getBytes());
364 }
365 } catch (java.io.FileNotFoundException exc) {
366 //System.err.println( P+" L EXC:"+ exc.toString()); exc.printStackTrace();
367
368 System.out.println( "File not found! "+ exc.toString());
369 Response response = new Http404Response(request);
370 nio.setSendData(response.getBytes());
371 } catch (Exception exc) {
372 // if a cancelled key is thrown, when the client closes, should i close
373 // the socket
374 System.err.println("Maybe i should close the channel here?? ");
375 System.err.println( P+" L EXC:"+ exc.toString()); exc.printStackTrace(); }
376 //nio.setSendData("HTTP/1.1 200 OK.\r\nContent-Length: "+content.length()+"\r\n\r\n"+ content);
377 }
378 static final String P="Processor";
379
380 /** parses an incoming date string as per RFC822.
381 * Sun, 06 Nov 1994 08:49:37 GMT ; RFC 822, updated by RFC 1123
382 * Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036
383 * Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format
384 *
385 * Should go into a new class HttpUtils.
386 */
387 public static java.util.Date RFC822Date (String sdate){
388
389 String df = null;
390 if (sdate.charAt(3)==',')
391 df="EEE, dd MMM yyyy HH:mm:ss 'GMT'";
392 else
393 if (sdate.charAt(6)==',')
394 df="EEEE, dd-MMM-yy HH:mm:ss 'GMT'";
395 else
396 df="EEE MMM dd HH:mm:ss yyyy";
397 SimpleDateFormat fo =
398 new SimpleDateFormat (df, Locale.US);
399 //fo.setTimeZone(TimeZone.getTimeZone("GMT"));
400 try {
401 Date d = fo.parse (sdate);
402 return d;
403 } catch (Exception exc) {
404 System.err.println( "403 RFC822Date :"+ exc.toString());
405 }
406 return null;
407
408 }
409 } // end of class
410