-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathWebServer.java
More file actions
executable file
·167 lines (150 loc) · 6.39 KB
/
WebServer.java
File metadata and controls
executable file
·167 lines (150 loc) · 6.39 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
/**
* WebServer Class
*
* Implements a multi-threaded web server supporting non-persistent connections.
*/
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.logging.*;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
public class WebServer extends Thread {
// global logger object, configures in the driver class
private static final Logger logger = Logger.getLogger("WebServer");
private static final String SERVER_NAME = "Prempreet's Server";
// check if the server was shutdown every 100ms; in the case of infinite timeout,
// shutdown the server after 1 second
private static final int CHECK_SHUTDOWN_INTERVAL = 100;
private static final long DEFAULT_SERVER_SHUTDOWN_TIME = 1000;
private static final int INFINITE = 0;
private boolean shutdown = false; // shutdown flag
private int port;
private String root;
private int timeout;
private long serverShutdownTime;
private ExecutorService executorService;
/**
* Constructor to initialize the web server
*
* @param port Server port at which the web server listens > 1024
* @param root Server's root file directory
* @param timeout Idle connection timeout in milli-seconds
*
*/
public WebServer(int port, String root, int timeout) {
this.port = port;
this.root = root;
this.timeout = timeout;
/*
* To deal with the case where the timeout is infinite, we cannot just wait
* an infinite amount of time for threads to terminate. In that case, wait a
* reasonable fixed amount of time.
*/
if (timeout == INFINITE) {
serverShutdownTime = DEFAULT_SERVER_SHUTDOWN_TIME;
}
else {
serverShutdownTime = timeout;
}
this.executorService = Executors.newCachedThreadPool();
}
/**
* Main method in the web server thread.
* The web server remains in listening mode
* and accepts connection requests from clients
* until it receives the shutdown signal.
*
*/
public void run() {
/*
* We need to keep track of all worker threads, but not all sockets opened up for clients.
* This is because the worker threads themselves will close each individual client socket.
*/
ServerSocket serverSocket = null;
/*
* if we can't even open a server socket or configure its shutdown interval,
* then we need to terminate the program immediately.
*/
try {
serverSocket = new ServerSocket(port);
serverSocket.setSoTimeout(CHECK_SHUTDOWN_INTERVAL);
}
catch (IOException e) {
e.printStackTrace();
cleanup(serverSocket);
}
while (!shutdown) {
try {
Socket newSocketForClient = serverSocket.accept();
System.out.println("New connection from " + newSocketForClient.getInetAddress() +
":" + newSocketForClient.getPort() + Utils.EOL);
WorkerThread workerThread = new WorkerThread(SERVER_NAME, root, newSocketForClient, timeout);
executorService.submit(workerThread);
}
/*
* This is expected behaviour; we do not terminate the program if there is a timeout.
* We simply check the loop condition again.
*/
catch (SocketTimeoutException e) {
}
catch (IOException e) {
/*
* This is an error in accepting a connection from A client; however, the server
* can still recover from this error (it can just ignore it and continue to try
* accepting connections from future clients). As a result, we don't terminate the program.
*/
e.printStackTrace();
}
}
cleanup(serverSocket);
}
/**
* Close all opened streams, sockets, and other resources before terminating the program.
*
* @param serverSocket the serverSocket listening for connections
*/
private void cleanup(ServerSocket serverSocket) {
try {
/*
* The java documentation was interpreted as follows:
* .shutdown() is an orderly shutdown in which previously submitted; gives the
* worker threads a chance to finish.
* .awaitTermination() waits a certain amount of time after .shutdown(); in other
* words, the "chance" the worker threads are given is limited to a specific time
* period.
* .shutdownNow() forcefully kills all worker threads that have still not terminated
* after the wait period.
*
* Therefore, all three methods are necessary:
* 1. Initiate shutdown
* 2. Limit shutdown to a certain period of time (so that we are not waiting forever)
* 3. Terminate threads that did not shut down within that certain period of time.
*
* This is necessary if the server takes a long time to transmit the file (for example,
* if you set the buffer size to be 1, and then quit the server, without shutdownNow
* the program abruptly stops after waiting for termination. However, with shutdownNow,
* all workerThreads invoke their cleanup method by throwing an InterruptedException).
* Without shutdownNow, the server will just stop, and there is no guarantee the workerThreads invoked
* their cleanup methods. shutdownNow forces this invocation by toggling the interrupt flag.
*/
executorService.shutdown();
executorService.awaitTermination(serverShutdownTime, TimeUnit.MILLISECONDS);
executorService.shutdownNow();
}
catch (InterruptedException e) {
e.printStackTrace();
}
Utils.closeGracefully(serverSocket);
}
/**
* Signals the web server to shutdown.
*
*/
public void shutdown() {
shutdown = true;
}
}