forked from RamseyK/httpserver
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathHTTPServer.cpp
More file actions
executable file
·673 lines (566 loc) · 21.5 KB
/
HTTPServer.cpp
File metadata and controls
executable file
·673 lines (566 loc) · 21.5 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
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
/**
httpserver
HTTPServer.cpp
Copyright 2011-2019 Ramsey Kant
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#include "HTTPServer.h"
/**
* Server Constructor
* Initialize state and server variables
*
* @param vhost_aliases List of hostnames the HTTP server will respond to
* @param port Port the vhost listens on
* @param diskpath Path to the folder the vhost serves up
* @param drop_uid UID to setuid to after bind(). Ignored if 0
* @param drop_gid GID to setgid to after bind(). Ignored if 0
*/
HTTPServer::HTTPServer(std::vector<std::string> vhost_aliases, int port, std::string diskpath, int drop_uid, int drop_gid) {
canRun = false;
listenSocket = INVALID_SOCKET;
listenPort = port;
kqfd = -1;
dropUid = drop_uid;
dropGid = drop_gid;
std::cout << "Port: " << port << std::endl;
std::cout << "Disk path: " << diskpath.c_str() << std::endl;
// Create a resource host serving the base path ./htdocs on disk
ResourceHost* resHost = new ResourceHost(diskpath);
hostList.push_back(resHost);
// Always serve up localhost/127.0.0.1 (which is why we only added one ResourceHost to hostList above)
char tmpstr[128];
sprintf(tmpstr, "localhost:%i", listenPort);
vhosts.insert(std::pair<std::string, ResourceHost*>(std::string(tmpstr).c_str(), resHost));
sprintf(tmpstr, "127.0.0.1:%i", listenPort);
vhosts.insert(std::pair<std::string, ResourceHost*>(std::string(tmpstr).c_str(), resHost));
// Setup the resource host serving htdocs to provide for the vhost aliases
for (std::string vh : vhost_aliases) {
if (vh.length() >= 122) {
std::cout << "vhost " << vh << " too long, skipping!" << std::endl;
continue;
}
std::cout << "vhost: " << vh << std::endl;
sprintf(tmpstr, "%s:%i", vh.c_str(), listenPort);
vhosts.insert(std::pair<std::string, ResourceHost*>(std::string(tmpstr).c_str(), resHost));
}
}
/**
* Server Destructor
* Removes all resources created in the constructor
*/
HTTPServer::~HTTPServer() {
// Loop through hostList and delete all ResourceHosts
while (!hostList.empty()) {
ResourceHost* resHost = hostList.back();
delete resHost;
hostList.pop_back();
}
vhosts.clear();
}
/**
* Start Server
* Initialize the Server Socket by requesting a socket handle, binding, and going into a listening state
*
* @return True if initialization succeeded. False if otherwise
*/
bool HTTPServer::start() {
canRun = false;
// Create a handle for the listening socket, TCP
listenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (listenSocket == INVALID_SOCKET) {
std::cout << "Could not create socket!" << std::endl;
return false;
}
// Set socket as non blocking
fcntl(listenSocket, F_SETFL, O_NONBLOCK);
// Populate the server address structure
// modify to support multiple address families (bottom): http://eradman.com/posts/kqueue-tcp.html
memset(&serverAddr, 0, sizeof(struct sockaddr_in)); // clear the struct
serverAddr.sin_family = AF_INET; // Family: IP protocol
serverAddr.sin_port = htons(listenPort); // Set the port (convert from host to netbyte order)
serverAddr.sin_addr.s_addr = INADDR_ANY; // Let OS intelligently select the server's host address
// Bind: Assign the address to the socket
if (bind(listenSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) != 0) {
std::cout << "Failed to bind to the address!" << std::endl;
return false;
}
// Optionally drop uid/gid if specified
if (dropUid > 0 && dropGid > 0) {
if (setgid(dropGid) != 0) {
std::cout << "setgid to " << dropGid << " failed!" << std::endl;
return false;
}
if (setuid(dropUid) != 0) {
std::cout << "setuid to " << dropUid << " failed!" << std::endl;
return false;
}
std::cout << "Successfully dropped uid to " << dropUid << " and gid to " << dropGid << std::endl;
}
// Listen: Put the socket in a listening state, ready to accept connections
// Accept a backlog of the OS Maximum connections in the queue
if (listen(listenSocket, SOMAXCONN) != 0) {
std::cout << "Failed to put the socket in a listening state" << std::endl;
return false;
}
// Setup kqueue
kqfd = kqueue();
if (kqfd == -1) {
std::cout << "Could not create the kernel event queue!" << std::endl;
return false;
}
// Have kqueue watch the listen socket
updateEvent(listenSocket, EVFILT_READ, EV_ADD, 0, 0, NULL);
canRun = true;
std::cout << "Server ready. Listening on port " << listenPort << "..." << std::endl;
return true;
}
/**
* Stop
* Disconnect all clients and cleanup all server resources created in start()
*/
void HTTPServer::stop() {
canRun = false;
if (listenSocket != INVALID_SOCKET) {
// Close all open connections and delete Client's from memory
for (auto& x : clientMap)
disconnectClient(x.second, false);
// Clear the map
clientMap.clear();
// Remove listening socket from kqueue
updateEvent(listenSocket, EVFILT_READ, EV_DELETE, 0, 0, NULL);
// Shudown the listening socket and release it to the OS
shutdown(listenSocket, SHUT_RDWR);
close(listenSocket);
listenSocket = INVALID_SOCKET;
}
if (kqfd != -1) {
close(kqfd);
kqfd = -1;
}
std::cout << "Server shutdown!" << std::endl;
}
/**
* Update Event
* Update the kqueue by creating the appropriate kevent structure
* See kqueue documentation for parameter descriptions
*/
void HTTPServer::updateEvent(int ident, short filter, u_short flags, u_int fflags, int data, void *udata) {
struct kevent kev;
EV_SET(&kev, ident, filter, flags, fflags, data, udata);
kevent(kqfd, &kev, 1, NULL, 0, NULL);
}
/**
* Server Process
* Main server processing function that checks for any new connections or data to read on
* the listening socket
*/
void HTTPServer::process() {
int nev = 0; // Number of changed events returned by kevent
Client* cl = NULL;
struct kevent read_kev, write_kev;
while (canRun) {
// Get a list of changed socket descriptors with a read event triggered in evList
// Timeout set in the header
nev = kevent(kqfd, NULL, 0, evList, QUEUE_SIZE, &kqTimeout);
if (nev <= 0)
continue;
// Loop through only the sockets that have changed in the evList array
for (int i = 0; i < nev; i++) {
if (evList[i].ident == (unsigned int)listenSocket) { // A client is waiting to connect
acceptConnection();
} else { // Client descriptor has triggered an event
cl = getClient(evList[i].ident); // ident contains the clients socket descriptor
if (cl == NULL) {
std::cout << "Could not find client" << std::endl;
continue;
}
// Client wants to disconnect
if (evList[i].flags & EV_EOF) {
disconnectClient(cl);
continue;
}
// Clear kevent structures
memset(&read_kev, 0, sizeof(struct kevent));
memset(&write_kev, 0, sizeof(struct kevent));
if (evList[i].filter == EVFILT_READ) {
//std::cout << "read filter " << evList[i].data << " bytes available" << std::endl;
// Read and process any pending data on the wire
readClient(cl, evList[i].data); // data contains the number of bytes waiting to be read
// Have kqueue disable tracking of READ events and enable tracking of WRITE events
updateEvent(evList[i].ident, EVFILT_READ, EV_DISABLE, 0, 0, NULL);
updateEvent(evList[i].ident, EVFILT_WRITE, EV_ENABLE, 0, 0, NULL);
} else if (evList[i].filter == EVFILT_WRITE) {
//std::cout << "write filter with " << evList[i].data << " bytes available" << std::endl;
// Write any pending data to the client - writeClient returns true if there is additional data to send in the client queue
if (!writeClient(cl, evList[i].data)) { // data contains number of bytes that can be written
//std::cout << "switch back to read filter" << std::endl;
// If theres nothing more to send, Have kqueue disable tracking of WRITE events and enable tracking of READ events
updateEvent(evList[i].ident, EVFILT_READ, EV_ENABLE, 0, 0, NULL);
updateEvent(evList[i].ident, EVFILT_WRITE, EV_DISABLE, 0, 0, NULL);
}
}
}
} // Event loop
} // canRun
}
/**
* Accept Connection
* When a new connection is detected in runServer() this function is called. This attempts to accept the pending
* connection, instance a Client object, and add to the client Map
*/
void HTTPServer::acceptConnection() {
// Setup new client with prelim address info
sockaddr_in clientAddr;
int clientAddrLen = sizeof(clientAddr);
int clfd = INVALID_SOCKET;
// Accept the pending connection and retrive the client descriptor
clfd = accept(listenSocket, (sockaddr*)&clientAddr, (socklen_t*)&clientAddrLen);
if (clfd == INVALID_SOCKET)
return;
// Set socket as non blocking
fcntl(clfd, F_SETFL, O_NONBLOCK);
// Instance Client object
Client *cl = new Client(clfd, clientAddr);
// Add kqueue event to track the new client socket for READ and WRITE events
updateEvent(clfd, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, NULL);
updateEvent(clfd, EVFILT_WRITE, EV_ADD | EV_DISABLE, 0, 0, NULL); // Disabled initially
// Add the client object to the client map
clientMap.insert(std::pair<int, Client*>(clfd, cl));
// Print the client's IP on connect
std::cout << "[" << cl->getClientIP() << "] connected" << std::endl;
}
/**
* Get Client
* Lookup client based on the socket descriptor number in the clientMap
*
* @param clfd Client socket descriptor
* @return Pointer to Client object if found. NULL otherwise
*/
Client* HTTPServer::getClient(int clfd) {
auto it = clientMap.find(clfd);
// Client wasn't found
if (it == clientMap.end())
return NULL;
// Return a pointer to the client object
return it->second;
}
/**
* Disconnect Client
* Close the client's socket descriptor and release it from the FD map, client map, and memory
*
* @param cl Pointer to Client object
* @param mapErase When true, remove the client from the client map. Needed if operations on the
* client map are being performed and we don't want to remove the map entry right away
*/
void HTTPServer::disconnectClient(Client *cl, bool mapErase) {
if (cl == NULL)
return;
std::cout << "[" << cl->getClientIP() << "] disconnected" << std::endl;
// Remove socket events from kqueue
updateEvent(cl->getSocket(), EVFILT_READ, EV_DELETE, 0, 0, NULL);
updateEvent(cl->getSocket(), EVFILT_WRITE, EV_DELETE, 0, 0, NULL);
// Close the socket descriptor
close(cl->getSocket());
// Remove the client from the clientMap
if (mapErase)
clientMap.erase(cl->getSocket());
// Delete the client object from memory
delete cl;
}
/**
* Read Client
* Recieve data from a client that has indicated that it has data waiting. Pass recv'd data to handleRequest()
* Also detect any errors in the state of the socket
*
* @param cl Pointer to Client that sent the data
* @param data_len Number of bytes waiting to be read
*/
void HTTPServer::readClient(Client *cl, int data_len) {
if (cl == NULL)
return;
// If the read filter triggered with 0 bytes of data, client may want to disconnect
// Set data_len to the Ethernet max MTU by default
if (data_len <= 0)
data_len = 1400;
HTTPRequest* req;
char* pData = new char[data_len];
bzero(pData, data_len);
// Receive data on the wire into pData
int flags = 0;
ssize_t lenRecv = recv(cl->getSocket(), pData, data_len, flags);
// Determine state of the client socket and act on it
if (lenRecv == 0) {
// Client closed the connection
std::cout << "[" << cl->getClientIP() << "] has opted to close the connection" << std::endl;
disconnectClient(cl);
} else if (lenRecv < 0) {
// Something went wrong with the connection
// TODO: check perror() for the specific error message
disconnectClient(cl);
} else {
// Data received: Place the data in an HTTPRequest and pass it to handleRequest for processing
req = new HTTPRequest((byte*)pData, lenRecv);
handleRequest(cl, req);
delete req;
}
delete [] pData;
}
/**
* Write Client
* Client has indicated it is read for writing. Write avail_bytes number of bytes to the socket if the send queue has an item
*
* @param cl Pointer to Client that sent the data
* @param avail_bytes Number of bytes available for writing in the send buffer
*/
bool HTTPServer::writeClient(Client* cl, int avail_bytes) {
if (cl == NULL)
return false;
int actual_sent = 0; // Actual number of bytes sent as returned by send()
int attempt_sent = 0; // Bytes that we're attempting to send now
int remaining = 0; // Size of data left to send for the item
bool disconnect = false;
byte* pData = NULL;
// The amount of available bytes to write, reported by the OS, cant really be trusted...
if (avail_bytes > 1400) {
// If the available amount of data is greater than the Ethernet MTU, cap it
avail_bytes = 1400;
} else if (avail_bytes == 0) {
// Sometimes OS reports 0 when its possible to send data - attempt to trickle data
// OS will eventually increase avail_bytes
avail_bytes = 64;
}
SendQueueItem* item = cl->nextInSendQueue();
if (item == NULL)
return false;
pData = item->getData();
remaining = item->getSize() - item->getOffset();
disconnect = item->getDisconnect();
if (avail_bytes >= remaining) {
// Send buffer is bigger than we need, rest of item can be sent
attempt_sent = remaining;
} else {
// Send buffer is smaller than we need, send the amount thats available
attempt_sent = avail_bytes;
}
// Send the data and increment the offset by the actual amount sent
actual_sent = send(cl->getSocket(), pData + (item->getOffset()), attempt_sent, 0);
if (actual_sent >= 0)
item->setOffset(item->getOffset() + actual_sent);
else
disconnect = true;
//std::cout << "[" << cl->getClientIP() << "] was sent " << actual_sent << " bytes " << std::endl;
// SendQueueItem isnt needed anymore. Dequeue and delete
if (item->getOffset() >= item->getSize())
cl->dequeueFromSendQueue();
if (disconnect) {
disconnectClient(cl);
return false;
}
return true;
}
/**
* Handle Request
* Process an incoming request from a Client. Send request off to appropriate handler function
* that corresponds to an HTTP operation (GET, HEAD etc) :)
*
* @param cl Client object where request originated from
* @param req HTTPRequest object filled with raw packet data
*/
void HTTPServer::handleRequest(Client *cl, HTTPRequest* req) {
// Parse the request
// If there's an error, report it and send a server error in response
if (!req->parse()) {
std::cout << "[" << cl->getClientIP() << "] There was an error processing the request of type: " << req->methodIntToStr(req->getMethod()) << std::endl;
// std::cout << req->getParseError().c_str() << std::endl;
sendStatusResponse(cl, Status(BAD_REQUEST), req->getParseError());
return;
}
std::cout << "[" << cl->getClientIP() << "] " << req->methodIntToStr(req->getMethod()) << " " << req->getRequestUri() << std::endl;
/*std::cout << "Headers:" << std::endl;
for(int i = 0; i < req->getNumHeaders(); i++) {
std::cout << "\t" << req->getHeaderStr(i) << std::endl;
}
std::cout << std::endl;*/
// Determine the appropriate vhost
ResourceHost* resHost = NULL;
std::string host = "";
// Retrieve the host specified in the request (Required for HTTP/1.1 compliance)
if (req->getVersion().compare(HTTP_VERSION_11) == 0) {
host = req->getHeaderValue("Host");
// All vhosts have the port appended, so need to append it to the host if it doesnt exist
if (host.find(":") == std::string::npos) {
host.append(":" + std::to_string(listenPort));
}
std::unordered_map<std::string, ResourceHost*>::const_iterator it = vhosts.find(host);
if (it != vhosts.end())
resHost = it->second;
} else {
// Temporary: HTTP/1.0 are given the first ResouceHost in the hostList
// TODO: Allow admin to specify a 'default resource host'
if (hostList.size() > 0)
resHost = hostList[0];
}
// ResourceHost couldnt be determined or the Host specified by the client was invalid
if (resHost == NULL) {
sendStatusResponse(cl, Status(BAD_REQUEST), "Invalid/No Host specified: " + host);
return;
}
// Send the request to the correct handler function
switch (req->getMethod()) {
case Method(HEAD):
case Method(GET):
handleGet(cl, req, resHost);
break;
case Method(OPTIONS):
handleOptions(cl, req);
break;
case Method(TRACE):
handleTrace(cl, req);
break;
default:
std::cout << "[" << cl->getClientIP() << "] Could not handle or determine request of type " << req->methodIntToStr(req->getMethod()) << std::endl;
sendStatusResponse(cl, Status(NOT_IMPLEMENTED));
break;
}
}
/**
* Handle Get or Head
* Process a GET or HEAD request to provide the client with an appropriate response
*
* @param cl Client requesting the resource
* @param req State of the request
* @param resHost Resource host to service the request
*/
void HTTPServer::handleGet(Client* cl, HTTPRequest* req, ResourceHost* resHost) {
// Check if the requested resource exists
std::string uri = req->getRequestUri();
Resource* r = resHost->getResource(uri);
if (r != NULL) { // Exists
std::cout << "[" << cl->getClientIP() << "] " << "Sending file: " << uri << std::endl;
HTTPResponse* resp = new HTTPResponse();
resp->setStatus(Status(OK));
resp->addHeader("Content-Type", r->getMimeType());
resp->addHeader("Content-Length", r->getSize());
// Only send a message body if it's a GET request. Never send a body for HEAD
if (req->getMethod() == Method(GET))
resp->setData(r->getData(), r->getSize());
bool dc = false;
// HTTP/1.0 should close the connection by default
if (req->getVersion().compare(HTTP_VERSION_10) == 0)
dc = true;
// If Connection: close is specified, the connection should be terminated after the request is serviced
std::string connection_val = req->getHeaderValue("Connection");
if (connection_val.compare("close") == 0)
dc = true;
sendResponse(cl, resp, dc);
delete resp;
delete r;
} else { // Not found
std::cout << "[" << cl->getClientIP() << "] " << "File not found: " << uri << std::endl;
sendStatusResponse(cl, Status(NOT_FOUND));
}
}
/**
* Handle Options
* Process a OPTIONS request
* OPTIONS: Return allowed capabilties for the server (*) or a particular resource
*
* @param cl Client requesting the resource
* @param req State of the request
*/
void HTTPServer::handleOptions(Client* cl, HTTPRequest* req) {
// For now, we'll always return the capabilities of the server instead of figuring it out for each resource
std::string allow = "HEAD, GET, OPTIONS, TRACE";
HTTPResponse* resp = new HTTPResponse();
resp->setStatus(Status(OK));
resp->addHeader("Allow", allow.c_str());
resp->addHeader("Content-Length", "0"); // Required
sendResponse(cl, resp, true);
delete resp;
}
/**
* Handle Trace
* Process a TRACE request
* TRACE: send back the request as received by the server verbatim
*
* @param cl Client requesting the resource
* @param req State of the request
*/
void HTTPServer::handleTrace(Client* cl, HTTPRequest *req) {
// Get a byte array representation of the request
unsigned int len = req->size();
byte* buf = new byte[len];
req->setReadPos(0); // Set the read position at the beginning since the request has already been read to the end
req->getBytes(buf, len);
// Send a response with the entire request as the body
HTTPResponse* resp = new HTTPResponse();
resp->setStatus(Status(OK));
resp->addHeader("Content-Type", "message/http");
resp->addHeader("Content-Length", len);
resp->setData(buf, len);
sendResponse(cl, resp, true);
delete resp;
delete[] buf;
}
/**
* Send Status Response
* Send a predefined HTTP status code response to the client consisting of
* only the status code and required headers, then disconnect the client
*
* @param cl Client to send the status code to
* @param status Status code corresponding to the enum in HTTPMessage.h
* @param msg An additional message to append to the body text
*/
void HTTPServer::sendStatusResponse(Client* cl, int status, std::string msg) {
HTTPResponse* resp = new HTTPResponse();
resp->setStatus(Status(status));
// Body message: Reason string + additional msg
std::string body = resp->getReason() + ": " + msg;
unsigned int slen = body.length();
char* sdata = new char[slen];
bzero(sdata, slen);
strncpy(sdata, body.c_str(), slen);
resp->addHeader("Content-Type", "text/plain");
resp->addHeader("Content-Length", slen);
resp->setData((byte*)sdata, slen);
sendResponse(cl, resp, true);
delete resp;
}
/**
* Send Response
* Send a generic HTTPResponse packet data to a particular Client
*
* @param cl Client to send data to
* @param buf ByteBuffer containing data to be sent
* @param disconnect Should the server disconnect the client after sending (Optional, default = false)
*/
void HTTPServer::sendResponse(Client* cl, HTTPResponse* resp, bool disconnect) {
// Server Header
resp->addHeader("Server", "httpserver/1.0");
// Time stamp the response with the Date header
std::string tstr;
char tbuf[36];
time_t rawtime;
struct tm* ptm;
time(&rawtime);
ptm = gmtime(&rawtime);
// Ex: Fri, 31 Dec 1999 23:59:59 GMT
strftime(tbuf, 36, "%a, %d %b %Y %H:%M:%S GMT", ptm);
tstr = tbuf;
resp->addHeader("Date", tstr);
// Include a Connection: close header if this is the final response sent by the server
if (disconnect)
resp->addHeader("Connection", "close");
// Get raw data by creating the response (we are responsible for cleaning it up in process())
byte* pData = resp->create();
// Add data to the Client's send queue
cl->addToSendQueue(new SendQueueItem(pData, resp->size(), disconnect));
}