-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathMyWebServer.java
More file actions
390 lines (339 loc) · 15.8 KB
/
MyWebServer.java
File metadata and controls
390 lines (339 loc) · 15.8 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
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.StringTokenizer;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
//Class representing the Web Server
//handling opening sockets and sending http requests to parser and handler
public class MyWebServer {
//Vars for adding to headers (Server name and HTTP version)
static final String HTTP_VERSION = "HTTP/1.1";
static final String SERVER_NAME = "MyWebServer";
//Var for marking incorrect date format - Error code 400
static int wrongDate = 0;
// Entry point of the program
public static void main(String[] args) throws IOException {
int port;
String rootDir;
//Check to make sure server is opened with port number and root directory
if(args.length != 2) {
System.out.println("Error: Expected 2 arguements got "+Integer.toString(args.length));
System.exit(0);
}
//args[0] = port number
//args[1] = ~/evaluationWeb (path)
port = Integer.parseInt(args[0]);
rootDir = args[1];
//Create socket
ServerSocket serverSocket = new ServerSocket(port);
System.out.println("Web Server Started on port " + port);
//Proccess HTTP requests
while(true){
//Create new client socket to send repsonses to the client
//Accept allows server socket to listen for a connection trying to be made by client
MyWebServer.wrongDate = 0;
Socket clientSocket = serverSocket.accept();
handleReq(clientSocket, rootDir);
}
}
//Method to bring in request strings coming from client requests and send them to be handled
private static void handleReq(Socket clientSocket, String rootDir) {
//input -> read in client request string
//output -> format output stream to write to client
try ( BufferedReader input = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
DataOutputStream output = new DataOutputStream(clientSocket.getOutputStream()) )
{
/*STEPS FOR A REQUEST
* 1. Read in and format reuqest string from user (getRequestString)
* 2. Parse through the reqest string's info to fufill the GET or HEAD request (parseRequest)
* 3. Server takes action on this request (handles or sends error message) (handleRequest)
* 4. Repsonse is then sent to user saying if the repsonse was correctly handled or error
*/
String requestString = getRequestString(input);
HttpReq httpReq = parseRequest(requestString, rootDir);
HttpResp httpResp = handleRequest(httpReq, rootDir);
sendResponse(output, httpReq, httpResp, clientSocket);
} catch (IOException e) {
System.err.println(e.getMessage());
} catch (ParseException e){
System.err.println(e.getMessage());
}
}
//Method to get request string
private static String getRequestString(BufferedReader input) throws IOException{
StringBuilder requestBuilder = new StringBuilder();
String line;
//Loop through read in lines to build request string
while((line = input.readLine()) != null && !line.isEmpty()) {
requestBuilder.append(line).append("\r\n");
}
//Return the builder as a string
return requestBuilder.toString();
}
//Method setup Http request object aka the Request Parser
private static HttpReq parseRequest(String requestString, String rootDir){
return new HttpReq(requestString, rootDir);
}
//Method to setup Http response object aka the Request Handler
private static HttpResp handleRequest(HttpReq httpReq, String rootDir) throws FileNotFoundException, ParseException{
return new HttpResp(httpReq, rootDir);
}
//Method to send response to client after request has been parsed and handled by server
private static void sendResponse(DataOutputStream output, HttpReq httReq, HttpResp httpResp, Socket client) throws IOException{
//Write to the client
output.writeBytes(httpResp.toString());
if("GET".equals(httReq.getMethod())) {
if (httpResp.getBody() != null) output.write(httpResp.getBody().readAllBytes());
}
output.flush();
client.close();
}
}
/*OBJECT: Handles all Http requests incoming from the server (request parser)*/
final class HttpReq {
//Important varaibles to store info for command(method), path and if-modified since date
private String method;
private String path;
private Date ifModifiedSince;
//Constructor to parse through the http request string and tokenize it
public HttpReq(String requestString, String rootDir){
//Tokenizing the request string
StringTokenizer tokenizer = new StringTokenizer(requestString);
//System.out.println("Request String: " + requestString);
//Method - stores first token in all uppercase (GET or HEAD request)
if (tokenizer.hasMoreTokens()) {
method = tokenizer.nextToken().toUpperCase();
//System.out.println("Command: " + method);
}
//Path - stores second token added onto input filepath + reqeusted path + index.html
if (tokenizer.hasMoreTokens()) {
String filePath = tokenizer.nextToken();
//System.out.println(filePath);
//Get rid of leading slash
path = rootDir + filePath;
if (path.endsWith("/")) {
path += "index.html";
}
//System.out.println("Path: " + path);
}
//Loop through rest of message to find If-modified-since header string to store.
//Formats date correctly as well to later compare with the last modified date in response
while (tokenizer.hasMoreTokens()) {
String header = tokenizer.nextToken();
// System.out.println(header);
if (header.equalsIgnoreCase("If-Modified-Since:")) {
try {
StringBuilder dateStringBuild = new StringBuilder();
while(tokenizer.hasMoreTokens()){
String token = tokenizer.nextToken();
if (token.equals("2024")){
dateStringBuild.append(token);
break;
}
else{dateStringBuild.append(token);}
if (tokenizer.hasMoreTokens()) {
dateStringBuild.append(" ");
}
}
String dateString = dateStringBuild.toString();
// System.out.println("Header: " + header);
// System.out.println("Date: " + dateString);
String PATTERN = "EEE MMM dd HH:mm:ss zzz yyyy";
DateFormat formatter = new SimpleDateFormat(PATTERN);
ifModifiedSince = formatter.parse(dateString);
break;
} catch (Exception e) {
//Handle parsing exception
ifModifiedSince = null;
MyWebServer.wrongDate = 1;
}
}
}
}
//GETTER METHODS: Used in Request Handler
//Getter method to return the command input by client - code only implements functionality for GET and HEAD
public String getMethod() {
return method;
}
//Getter method to return path
public String getPath() {
return path;
}
//Getter method to return the date since last modified
public Date getIfModifiedSince() {
return ifModifiedSince;
}
}
/*OBJECT: Handles all Http responses (request handler) */
final class HttpResp {
//Variables to write repsonse
private int statusCode;
private String statusMessage;
private String date;
private String server;
private String lastModified;
private long contentLength;
private FileInputStream responseBody;
//Constructor to taken in parsed request and root directory to format if request is valid and can be handled
// or if an error message needs to be sent to the user
public HttpResp(HttpReq httpReq, String rootDir) throws FileNotFoundException, ParseException {
this.date = new Date().toString();
this.date = date.replace("EDT", "EST");
this.server = MyWebServer.SERVER_NAME;
String method = httpReq.getMethod();
if ("GET".equals(method)) {
File file = new File(httpReq.getPath());
//System.out.println("File:"+file);
if(MyWebServer.wrongDate == 1){
this.statusCode = 400;
this.statusMessage = "Bad Request";
// this.responseBody = "<h1>404 Not Found</h1>";
return;
}
//Check if the requested file exists
if (!file.exists() || !file.isFile()) {
this.statusCode = 404;
this.statusMessage = "Not Found";
//System.out.println("File doesn't exist!");
//this.responseBody = "<h1>404 Not Found</h1>";
return;
}
//File exists check its length and when it was modified
this.contentLength = file.length();
this.lastModified = formatDate(file.lastModified());
this.lastModified = lastModified.replace("EDT", "EST");
//Check if If-Modified-Since header is provided and file modification date is after that date (Might only need for HEAD)
Date ifModifiedSince = httpReq.getIfModifiedSince();
Date lastModifiedDate = StrToDate(lastModified);
// System.out.println(ifModifiedSince);
// System.out.println(lastModifiedDate);
//System.out.println("Has file been modified since: "+ifModifiedSince.getTime());
if (ifModifiedSince != null) {
int comparison = ifModifiedSince.compareTo(lastModifiedDate);
if (comparison > 0)
{
this.statusCode = 304;
this.statusMessage = "Not Modified";
return;
}
}
//File exists and needs to be sent
this.statusCode = 200;
this.statusMessage = "OK";
//Get object file (path) and store it to the body
FileInputStream object = new FileInputStream(file.toString());
this.responseBody = object;
return;
}
if ("HEAD".equals(method)) {
//Similar logic as GET but without sending the file content (body)
//Set appropriate status code and message for HEAD request
File file = new File(httpReq.getPath());
//System.out.println(file.toString());
if(MyWebServer.wrongDate == 1){
this.statusCode = 400;
this.statusMessage = "Bad Request";
// this.responseBody = "<h1>404 Not Found</h1>";
return;
}
//Check if the requested file exists
if (!file.exists() || !file.isFile()) {
//if (!file.exists() && !file.isFile()) System.out.println("Both false??");
this.statusCode = 404;
this.statusMessage = "Not Found";
// this.responseBody = "<h1>404 Not Found</h1>";
return;
}
//File exists check its length and when it was modified
this.contentLength = file.length();
this.lastModified = formatDate(file.lastModified());
this.lastModified = lastModified.replace("EDT", "EST");
//System.out.println(lastModified);
//Check if If-Modified-Since header is provided and file modification date is after that date
Date ifModifiedSince = httpReq.getIfModifiedSince();
Date lastModifiedDate = StrToDate(lastModified);
// System.out.println(ifModifiedSince);
// System.out.println(lastModifiedDate);
//System.out.println("Has file been modified since: "+ifModifiedSince.getTime());
if (ifModifiedSince != null) {
int comparison = ifModifiedSince.compareTo(lastModifiedDate);
if (comparison > 0)
{
this.statusCode = 304;
this.statusMessage = "Not Modified";
return;
}
}
//File exists and needs to be sent
this.statusCode = 200;
this.statusMessage = "OK";
return;
}
//Unsupported method, set 501 Not Implemented - Didn't match GET or HEAD (Ex: POST)
this.statusCode = 501;
this.statusMessage = "Not Implemented";
return;
}
//Helper method to format the lastModifed date
private String formatDate(long timestamp){
SimpleDateFormat PATTERN = new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy");
Date date = new Date(timestamp);
String formattedDate = PATTERN.format(date);
return formattedDate;
}
//Helper method to convert lastModifed date string into a date for comparison with If-Modified-Since
private Date StrToDate(String lastModifed) throws ParseException{
String dateString = lastModified;
SimpleDateFormat PATTERN = new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy");
Date date = PATTERN.parse(dateString);
return date;
}
@Override
public String toString() {
StringBuilder responseBuilder = new StringBuilder();
//Build HTTP/1.1 <CODE> and status message - request line
responseBuilder.append(MyWebServer.HTTP_VERSION).append(" ").append(statusCode).append(" ").append(statusMessage).append("\r\n");
//Check to make sure we need headers (only for OK status)
if(this.statusCode != 404 && this.statusCode != 304 && this.statusCode != 501 && this.statusCode != 400) {
//Headers
responseBuilder.append("Date: ").append(date).append("\r\n");
responseBuilder.append("Server: ").append(server).append("\r\n");
responseBuilder.append("Last-Modified: ").append(lastModified).append("\r\n");
responseBuilder.append("Content-Length: ").append(contentLength).append("\r\n");
responseBuilder.append("\r\n");
}
else if(this.statusCode == 304){
responseBuilder.append("Date: ").append(date).append("\r\n");
responseBuilder.append("\r\n");
responseBuilder.append("Error 304: Not modified");
}
else if(this.statusCode == 404){
responseBuilder.append("Date: ").append(date).append("\r\n");
responseBuilder.append("Server: ").append(server).append("\r\n");
responseBuilder.append("\r\n");
responseBuilder.append("Error 404: File not found");
}
else if(this.statusCode == 501){
responseBuilder.append("\r\n");
responseBuilder.append(MyWebServer.HTTP_VERSION).append(" ").append(statusCode).append(" ").append(statusMessage).append("\r\n");
}
else if(this.statusCode == 400){
responseBuilder.append("\r\n");
responseBuilder.append("Error 400: Bad request");
}
return responseBuilder.toString();
}
//Method to help build message body for GET reqeusts
public FileInputStream getBody(){
return responseBody;
}
}