-
Notifications
You must be signed in to change notification settings - Fork 0
/
server.js
277 lines (224 loc) · 7.45 KB
/
server.js
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
//#!/bin/env node
"use strict";
const ROOT_DIRECTORY = __dirname + "/";
const HTTP_DIRECTORY = ROOT_DIRECTORY + "HACC-AP/";
const INCLUDE_DIRECTORY = ROOT_DIRECTORY + "include/";
const TEMPLATE_DIRECTORY = ROOT_DIRECTORY + "template/";
const ALLOWED_METHODS = ["GET", "POST"];
const LISTEN_ADDRESS = "0.0.0.0";
const LISTEN_PORT = 8080;
let application = require("express")();
let http = require("http");
let server = http.createServer(application);
let fs = require("fs");
let ServerError = require(INCLUDE_DIRECTORY + "serverErrors");
let template = require(INCLUDE_DIRECTORY + "template");
let utilities = require(INCLUDE_DIRECTORY + "utilities");
let socrata = require(INCLUDE_DIRECTORY + "socrataApi");
// Add headers
/*
application.use(function (request, response, next) {
// Website you wish to allow to connect
response.setHeader("Access-Control-Allow-Origin", "http://we.dont.have.a.domain/");
// Request methods you wish to allow
response.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PUT, PATCH, DELETE");
// Request headers you wish to allow
response.setHeader("Access-Control-Allow-Headers", "X-Requested-With,content-type");
// Set to true if you need the website to include cookies in the requests sent
// to the API (e.g. in case you use sessions)
// response.setHeader("Access-Control-Allow-Credentials", true);
// Pass to next layer of middleware
next();
});
*/
application.use("/api/get/:id", (request, response, next) => {
socrata.get(request.params.id, (error, data) => {
if (error) {
let httpError = new Error("400 Bad Request");
httpError.status = 400;
httpError._type = "API";
httpError._reason = "Invalid ID specified";
httpError._errorObject = error;
httpError._method = request.method;
httpError._originalPath = request.path;
// Trigger the error handler chain
next(httpError);
return;
}
response.setHeader("Content-Type", "text/plain");
response.send(data);
});
});
// Send plain-text 404
application.use("/api/*", (request, response, next) => {
let httpError = new Error("404 Bad Request");
httpError.status = 404;
httpError._type = "API";
httpError._reason = "Invalid API endpoint";
// Trigger the error handler chain
next(httpError);
return;
});
// Attempt to serve any file requested from the HACC-AP directory
application.use((request, response, next) => {
// Handle only GET requests
if (request.method != "GET") {
// Pass the request down the chain
next();
return;
}
// Removes the leading slash
let path = request.path.substring("/".length);
if (request.path.charAt(request.path.length - 1) == "/") {
// Try to serve an index.html if the request is for a directory
path += "index";
}
// Look for a file with the same path but with the .html extension appended
// This allows /test to point to /test.html
fs.access(HTTP_DIRECTORY + path + ".html", fs.constants.R_OK, (error) => {
if (error) {
// Look for the explicit file
fs.access(HTTP_DIRECTORY + path, fs.constants.R_OK, (error) => {
if (error) {
let httpError = new Error("404 Not Found");
httpError.status = 404;
httpError._errorObject = error;
httpError._method = request.method;
httpError._originalPath = request.path;
httpError._impliedPath = path;
// Trigger the error handler chain
next(httpError);
return;
}
if (path.substring(path.length - "index.html".length) != "index.html" && path.substring(path.length - ".html".length) == ".html") {
// Send a 301 Moved Permanently
response.status(301);
response.setHeader("Location", path.substring(0, path.length - ".html".length));
response.send("");
} else {
// Send the file
response.sendFile(HTTP_DIRECTORY + path)
}
});
return;
}
response.sendFile(HTTP_DIRECTORY + path + ".html")
});
});
application.use((request, response, next) => {
// Handle only POST requests
if (request.method != "POST") {
// Pass the request down the chain
next();
return;
}
response.setHeader("Content-Type", "text/plain");
response.send("POST request received!");
});
// Catch 405 errors
// Supported methods are in ALLOWED_METHODS
application.use((request, response, next) => {
// Filter for methods
if (request.method in ALLOWED_METHODS) {
// Pass the request down the chain
next();
return;
}
let error = new Error("405 Method Not Allowed");
error.status = 405;
error._method = request.method;
error._originalPath = request.path;
// Trigger the error handler chain
next(error);
});
// Catch 500 errors (catch-all)
application.use((request, response, next) => {
let error = new Error("500 Internal Server Error");
error.status = 500;
error._method = request.method;
error._originalPath = request.path;
// Trigger the error handler chain
next(error);
});
// Handle all API errors
application.use((error, request, response, next) => {
if (error._type != "API") {
next(error);
return;
}
response.status(error.status);
response.setHeader("Content-Type", "text/plain");
response.send(JSON.stringify({
status: error.status,
message: error._reason
}));
});
// Handle errors
application.use((mainError, request, response, next) => {
// If there is a specific error object for the given error, use it. Otherwise, use 500
// Internal Server Error
let errorType = mainError.status in ServerError ? ServerError[mainError.status] : ServerError[500];
// Return a return the correct status code header
response.status(errorType.code);
utilities.randomString(25, (error, errorID) => {
if (error) {
// Couldn't generate an ID
console.error("Application error reference ID: " + errorID);
console.error(mainError);
console.error("Error generating error ID:");
console.error(error);
// Send a plain text error
response.setHeader("Content-Type", "text/plain");
response.send(errorType.code + " " + errorType.name);
return;
}
template.parseFile(TEMPLATE_DIRECTORY + "error", {
errorCode: errorType.code,
errorName: errorType.name,
errorMessage: errorType.message,
errorInformation: () => {
if (typeof errorType.information == "function") {
return errorType.information(request.method, request.path);
}
return errorType.information;
},
errorHelpText: () => {
if (typeof errorType.helpText == "function") {
return errorType.helpText(errorID);
}
return errorType.helpText;
}
}, (error, data) => {
if (error) {
// Couldn't read or parse the error template
console.error("Application error reference ID: " + errorID);
console.error(mainError);
console.error("Error getting template:");
console.error(error);
// Send a plain text error
response.setHeader("Content-Type", "text/plain");
response.send(errorType.code + " " + errorType.name + "\nError Reference ID: " + errorID);
return;
}
console.error("Application error reference ID: " + errorID);
console.error(mainError);
console.error("");
response.send(data);
});
});
});
server.listen(LISTEN_PORT, LISTEN_ADDRESS, () => {
console.log("Listening on " + LISTEN_ADDRESS + ":" + LISTEN_PORT);
});
// Catch SIGTERM sent to process
process.on("SIGTERM", () => {
// If server.close doesn't close within 5 seconds, exit
setTimeout(() => {
process.exit(0);
}, 5000);
// Stop accepting new connections, and finish serving already established connections
server.close(() => {
// Once all requests have been served, exit
process.exit(0);
});
});