-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathmain.js
325 lines (287 loc) · 10.9 KB
/
main.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
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
/*
*
*/
var DukeOfHazardLAF = "<head><style>table { font-family: arial, sans-serif; border-collapse: collapse; ;}" +
"td, th { border: 1px solid #dddddd; text-align: left; padding: 8px;}tr:nth-child(even) { background-color: #dddddd;}" +
"title {font-family: arial, sans-serif;} " +
"h1 {font-family: arial, sans-serif;}" +
"h2 {font-family: arial, sans-serif;}" +
"p {font-family: arial, sans-serif;}" +
"</style><title>Duke of Hazard</title></head>";
// For ease of testing from inside the firewall
var useProxy = true;
var weatherHost = 'graphical.weather.gov';
var weatherPort = 80;
var proxyHost = 'proxy-host';
var proxyPort = 80;
/**
* Composes a response using the provided zip code.
* @param {type} request
* @param {type} response
* @param {type} zipcode
* @return {undefined}
*/
function respondUsingZip(request, response, zipcode) {
// console.log('In respondUsingZip with zipcode = ' + zipcode);
processWeather(weatherPathForZip(zipcode), response, respond, zipcode);
}
/**
* Callback which converts weather info retrieved from the NWS site into
* HTML output and writes it to the HTTP response.
* @param {type} response
* @param {type} location
* @param {type} weatherInfo
* @return {undefined}
*/
function respond(response, location, weatherInfo) {
//console.log('In respond with weatherInfo ========\n' + weatherInfo + '\n========\n');
response.writeHead(200, {'Content-Type': 'text/html'});
var alerts = extractAlerts(weatherInfo);
response.write(formatAlerts(alerts, location));
//console.log('About to end response');
response.end();
}
/**
* Extracts alert times and alerts from all the weather info returned by the query to the NWS site
* and retains only the earliest entry that refer to the same alert.
* @param {type} weatherInfo
* @return {Array|trimHazards.trimmedHazards}
*/
function extractAlerts(weatherInfo) {
var startValidTimes = extractStartValidTimes(weatherInfo);
var hazards = extractHazards(weatherInfo);
return trimHazards(startValidTimes, hazards);
}
/**
* Formats the provided alert objects as HTML for display to the client.
* @param {type} alerts
* @param {type} location
* @return {DukeOfHazardLAF|String}
*/
function formatAlerts(alerts, location) {
var result = DukeOfHazardLAF;
result += "<body><h1>Duke of Hazard</h1><h2>Alerts for " + location + "</h2><table><tr><th>Time</th><th>Alert</th></tr>\n";
for (var i = 0; i < alerts.length; i++) {
result += "<tr><td>" + makeLink(alerts[i].time.toUTCString(),alerts[i].hazard.url) + "</td>";
result += "<td>" + makeLink(alerts[i].hazard.phen + " " + alerts[i].hazard.sign, alerts[i].hazard.url) + "</td></tr>";
}
result += "</table>";
if (alerts.length > 0) {
result += "<iframe src='" + alerts[0].hazard.url + "' width='100%' height='500' frameborder='1' allowfullscreen sandbox>" +
"</iframe>";
}
result += "</body>";
//console.log("formatAlerts result is \n-----" + result + "\n-----");
return result;
}
function makeLink(text, url) {
return "<a href='" + url + "'>" + text + "</a>";
}
/**
* Extracts the start times for the alert entries into an array of Date objects.
* @param {type} weatherInfo
* @return {Array|extractStartValidTimes.times}
*/
function extractStartValidTimes(weatherInfo) {
var re = new RegExp("<start-valid-time>(.*?)</start-valid-time>","g");
var matcher;
var times = [];
while (matcher = re.exec(weatherInfo)) {
var timeString = matcher[1];
var time = new Date(timeString);
times.push(time);
}
return times;
}
/**
* Extracts hazard entries from the returned weather info.
*
* The returned array has to be paralle with the array of timestamps (the returned
* data expresses them separately in the XML) so empty hazard entries still need
* to be represented in the result. The returned array contains nulls in those slots.
*
* @param {type} weatherInfo
* @return {Array|extractHazards.hazards}
*/
function extractHazards(weatherInfo) {
var re = new RegExp("<hazard-conditions>([\\s\\S]*?)</hazard-conditions>|<hazard-conditions/>","g");
var innerRE = new RegExp('<hazard.*?phenomena="(.*?)".*?significance="(.*?)"[\\s\\S]*?<hazardTextURL>(.*?)</hazardTextURL>','g');
var outerMatcher;
var hazards = [];
while (outerMatcher = re.exec(weatherInfo)) {
var innerMatcher;
var hazardInfo = {
phen: null,
sign: null,
url: null
};
while (innerMatcher = innerRE.exec(outerMatcher[1])) {
// console.log(" phen is " + innerMatcher[1] + ", signif is " + innerMatcher[2] +
// ", url is " + innerMatcher[3]);
hazardInfo.phen = innerMatcher[1];
hazardInfo.sign = innerMatcher[2];
hazardInfo.url = innerMatcher[3];
}
if (hazardInfo.phen === null) {
hazardInfo = null;
}
hazards.push(hazardInfo);
// if (hazardInfo == null) {
// console.log("Pushed null hazard");
// } else {
// console.log("Pushed hazard " + hazardInfo.phen + " " + hazardInfo.sign + " " + hazardInfo.url);
// }
}
return hazards;
}
/**
* Converts parallel arrays of times and hazards into a single result
* array that is pruned so any given hazard appears only once. (The input
* arrays might refer to the same hazard multiple times.)
* @param {type} times
* @param {type} hazards
* @return {Array|trimHazards.trimmedHazards}
*/
function trimHazards(times, hazards) {
var trimmedHazards = [];
var previousHazard = null;
for (var i = 0; i < hazards.length; i++) {
if (hazards[i] != null) {
var trimmedHazard = {
time: times[i],
hazard: hazards[i]
};
if (previousHazard == null || previousHazard.hazard.url !== trimmedHazard.hazard.url) {
trimmedHazards.push(trimmedHazard);
}
previousHazard = trimmedHazard;
}
}
return trimmedHazards;
}
function weatherPathForZip(zipcode) {
var path = weatherPathPrefix() + 'zipCodeList=' + zipcode + weatherPathSuffix();
console.log('Computed path for zip is ' + path);
return path;
}
function weatherPathForLatLong(latitude, longitude) {
var path = weatherPathPrefix() + 'lat=' + latitude + '&lon=' + longitude + weatherPathSuffix();
console.log('Computed path for lat/long is ' + path);
return path;
}
function weatherPathSuffix() {
var now = new Date();
var oneHourAgo = new Date();
oneHourAgo.setTime(oneHourAgo.getTime() - 60 * 60 * 1000);
var oneHourAgoString = oneHourAgo.toISOString();
var nowString = now.toISOString();
return "&product=time-series&begin=" + oneHourAgoString.substr(0,oneHourAgoString.length-1) +
// "&end=" + nowString.substr(0,nowString.length-1) +
"&wwa=wwa";
}
function weatherPathPrefix() {
return '/xml/sample_products/browser_interface/ndfdXMLclient.php?';
}
/**
* Sends a ReST request to the NWS site using the path specified (it varies
* based on how the user is requesting the data -- zip code vs. lat/long) and
* passes the returned response to the callback for processing once the full
* response has arrived.
* @param {type} path
* @param {type} response
* @param {type} callback
* @param {type} location
* @return {undefined}
*/
function processWeather(path, response, callback, location) {
var options = {
host: weatherHost,
port: weatherPort,
path: path,
method: 'GET'
};
var weatherURL = 'http://' + weatherHost + ":" + weatherPort + path;
if (useProxy) {
options.host = proxyHost;
options.port = proxyPort;
options.path = weatherURL;
}
// console.log('About to send request to get weather: ' + weatherURL);
var req = http.request(options, function (res) {
// console.log('Just inside the callback for response event');
res.setEncoding('utf8');
var result = '';
res.on('data', (chunk) => {
result += chunk;
//console.log('Received weather info \n--------\n' + chunk + '\n--------\n');
});
res.on('end', () => {
//console.log('Found end of weather response; about to invoke callback');
callback(response, location, result);
});
});
req.end();
req.on('error', (e) => {
console.log('Error during request: ' + e.message);
});
}
function respondUsingLatLong(request, response, latitude, longitude) {
processWeather(weatherPathForLatLong(latitude, longitude), response, respond, latitude + ',' + longitude);
}
function handleRequest(request, response) {
var parsedURL = url.parse(request.url, true /* parseQueryString */);
var path = parsedURL.path;
console.log('In handleRequest with path ' + path );
var query = parsedURL.query;
var zipcode = query.zip;
if (typeof query.zipcode !== "undefined" && query.zipcode !== "") {
zipcode = query.zipcode;
}
var latitude = query.lat;
var longitude = query.lon;
if (typeof query.long !== "undefined") {
longitude = query.long;
}
if (typeof zipcode !== "undefined" && zipcode !== "") {
respondUsingZip(request, response, zipcode);
} else if (typeof latitude !== "undefined" && typeof longitude !== "undefined") {
respondUsingLatLong(request, response, latitude, longitude);
} else if (parsedURL.path === "/") {
respondWithPrompt(request, response);
} else if (parsedURL.path.substr(1,1) !== '?') {
respondWithPathContent(request, path, response);
} else {
respondWithPrompt(request, response);
}
//console.log('Zip is ' + zipcode + ', lat/long are ' + latitude + "/" + longitude);
// response.writeHead(200, {'Content-Type': 'text/plain'});
// response.end('Hello World\n');
}
function respondWithPrompt(request, response) {
respondWithPathContent(request, "/public/index.html", response);
}
function respondWithPathContent(request, path, response) {
console.log('Responding with content for path ' + path);
fs.readFile(path.substr(1,path.length - 1), function (error, pgResp) {
if (error) {
response.writeHead(404);
response.write('Sorry, I could not find the content ' + path);
} else {
response.writeHead(200, { 'Content-Type': 'text/html' });
response.write(pgResp);
}
response.end();
});
// response.writeHead(200, {'Content-Type': 'text/plain'});
// response.write('Please specify either a zip code or a latitude/longitude pair');
// response.end();
}
var http = require('http');
var url = require('url');
var os = require('os');
var fs = require('fs');
var port=8080;
http.createServer(function (request, response) {
handleRequest(request, response);
}).listen(port);
console.log('Server running at http://127.0.0.1:'+port);